From b3265f9db4debea9e305147eecfab50473740b03 Mon Sep 17 00:00:00 2001 From: Adam Dougal Date: Sun, 15 Jan 2017 18:15:15 +0000 Subject: [PATCH] Adds batch execution matcher Also adds ability to retrieve variable types of prepared statements executed within batches. Fixes #107. --- .../http/client/BatchExecution.java | 22 +- .../scassandra/http/client/BatchQuery.java | 25 +- .../matchers/BatchExecutionMatcher.java | 84 ++ .../org/scassandra/matchers/Matchers.java | 5 + .../http/client/ActivityClientTest.java | 2 +- .../matchers/BatchStatementMatcherTest.java | 802 ++++++++++++++++++ .../BatchActivityVerificationTest.java | 4 +- .../main/java/batches/BatchPrimingTest.java | 26 + .../server/actors/BatchHandler.scala | 2 +- .../server/priming/ActivityLog.scala | 2 +- .../priming/json/PrimingJsonImplicits.scala | 2 +- .../server/actors/BatchHandlerTest.scala | 17 +- 12 files changed, 978 insertions(+), 15 deletions(-) create mode 100644 java-client/src/main/java/org/scassandra/matchers/BatchExecutionMatcher.java create mode 100644 java-client/src/test/java/org/scassandra/matchers/BatchStatementMatcherTest.java diff --git a/java-client/src/main/java/org/scassandra/http/client/BatchExecution.java b/java-client/src/main/java/org/scassandra/http/client/BatchExecution.java index 1bc25df6..bdaacc0c 100644 --- a/java-client/src/main/java/org/scassandra/http/client/BatchExecution.java +++ b/java-client/src/main/java/org/scassandra/http/client/BatchExecution.java @@ -19,6 +19,8 @@ import java.util.Arrays; import java.util.List; +import static org.scassandra.http.client.BatchType.LOGGED; + public final class BatchExecution { private final List batchQueries; @@ -39,6 +41,10 @@ public String getConsistency() { return consistency; } + public BatchType getBatchType() { + return batchType; + } + @Override public boolean equals(Object o) { @@ -78,8 +84,8 @@ public static BatchExecutionBuilder builder() { public static class BatchExecutionBuilder { private List batchQueries; - private String consistency; - private BatchType batchType; + private String consistency = "ONE"; + private BatchType batchType = LOGGED; private BatchExecutionBuilder() { } @@ -102,11 +108,23 @@ public BatchExecutionBuilder withBatchQueries(BatchQuery.BatchQueryBuilder... ba return this; } + /** + * Defaults to ONE if not set. + * + * @param consistency Query consistency + * @return this builder + */ public BatchExecutionBuilder withConsistency(String consistency) { this.consistency = consistency; return this; } + /** + * Defaults to LOGGED if not set. + * + * @param batchType Batch type + * @return this builder + */ public BatchExecutionBuilder withBatchType(BatchType batchType) { this.batchType = batchType; return this; diff --git a/java-client/src/main/java/org/scassandra/http/client/BatchQuery.java b/java-client/src/main/java/org/scassandra/http/client/BatchQuery.java index e998a0a3..d31edec4 100644 --- a/java-client/src/main/java/org/scassandra/http/client/BatchQuery.java +++ b/java-client/src/main/java/org/scassandra/http/client/BatchQuery.java @@ -17,18 +17,23 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import org.scassandra.cql.CqlType; + public final class BatchQuery { private final String query; private final BatchQueryKind batchQueryKind; private final List variables; + private final List variableTypes; - private BatchQuery(String query, BatchQueryKind batchQueryKind, List variables) { + private BatchQuery(String query, BatchQueryKind batchQueryKind, List variables, List variableTypes) { this.query = query; this.batchQueryKind = batchQueryKind; this.variables = variables; + this.variableTypes = variableTypes; } public String getQuery() { @@ -43,12 +48,17 @@ public List getVariables() { return variables; } + public List getVariableTypes() { + return variableTypes; + } + @Override public String toString() { return "BatchQuery{" + "query='" + query + '\'' + ", batchQueryKind=" + batchQueryKind + ", variables=" + variables + + ", variableTypes=" + variableTypes + '}'; } @@ -61,8 +71,8 @@ public boolean equals(Object o) { if (query != null ? !query.equals(that.query) : that.query != null) return false; if (batchQueryKind != that.batchQueryKind) return false; - return !(variables != null ? !variables.equals(that.variables) : that.variables != null); - + if (variables != null ? !variables.equals(that.variables) : that.variables != null) return false; + return variableTypes != null ? variableTypes.equals(that.variableTypes) : that.variableTypes == null; } @Override @@ -70,6 +80,7 @@ public int hashCode() { int result = query != null ? query.hashCode() : 0; result = 31 * result + (batchQueryKind != null ? batchQueryKind.hashCode() : 0); result = 31 * result + (variables != null ? variables.hashCode() : 0); + result = 31 * result + (variableTypes != null ? variableTypes.hashCode() : 0); return result; } @@ -81,6 +92,7 @@ public static class BatchQueryBuilder { private String query; private BatchQueryKind batchQueryKind = BatchQueryKind.query; private List variables = new ArrayList(); + private List variableTypes = Collections.emptyList(); private BatchQueryBuilder() { } @@ -100,8 +112,13 @@ public BatchQueryBuilder withVariables(Object... variables) { return this; } + public BatchQueryBuilder withVariableTypes(CqlType... variableTypes) { + this.variableTypes = Arrays.asList(variableTypes); + return this; + } + public BatchQuery build() { - return new BatchQuery(query, batchQueryKind, variables); + return new BatchQuery(query, batchQueryKind, variables, variableTypes); } } } diff --git a/java-client/src/main/java/org/scassandra/matchers/BatchExecutionMatcher.java b/java-client/src/main/java/org/scassandra/matchers/BatchExecutionMatcher.java new file mode 100644 index 00000000..b4657041 --- /dev/null +++ b/java-client/src/main/java/org/scassandra/matchers/BatchExecutionMatcher.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 Christopher Batey and Dogan Narinc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.scassandra.matchers; + +import org.hamcrest.Description; +import org.scassandra.cql.CqlType; +import org.scassandra.http.client.BatchExecution; +import org.scassandra.http.client.BatchQuery; + +import java.util.List; + +public class BatchExecutionMatcher extends ScassandraMatcher, BatchExecution> { + + private final BatchExecution expectedBatchExecution; + + BatchExecutionMatcher(BatchExecution expectedBatchExecution) { + if (expectedBatchExecution == null) + throw new IllegalArgumentException("null expectedBatchExecution"); + this.expectedBatchExecution = expectedBatchExecution; + } + + @Override + public void describeMismatchSafely(List batchExecutions, Description description) { + description.appendText("the following batches were executed: "); + for (BatchExecution batchExecution : batchExecutions) { + description.appendText("\n" + batchExecution); + } + } + + @Override + public void describeTo(Description description) { + description.appendText("Expected batch " + expectedBatchExecution + " to be executed"); + } + + @Override + protected boolean match(BatchExecution actualBatchExecution) { + if (!actualBatchExecution.getConsistency().equals(expectedBatchExecution.getConsistency())) { + return false; + } + + if (!actualBatchExecution.getBatchType().equals(expectedBatchExecution.getBatchType())) { + return false; + } + + List expectedBatchQueries = expectedBatchExecution.getBatchQueries(); + List actualBatchQueries = actualBatchExecution.getBatchQueries(); + + for (int i = 0; i < expectedBatchExecution.getBatchQueries().size(); i++) { + BatchQuery expectedBatchQuery = expectedBatchQueries.get(0); + BatchQuery actualBatchQuery = actualBatchQueries.get(0); + + if (!expectedBatchQuery.getQuery().equals(actualBatchQuery.getQuery())) { + return false; + } + + if (!expectedBatchQuery.getBatchQueryKind().equals(actualBatchQuery.getBatchQueryKind())) { + return false; + } + + List expectedVariables = expectedBatchQuery.getVariables(); + List actualVariableTypes = actualBatchQuery.getVariableTypes(); + List actualVariables = actualBatchQuery.getVariables(); + + if (!checkVariables(expectedVariables, actualVariableTypes, actualVariables)) { + return false; + } + } + + return true; + } +} diff --git a/java-client/src/main/java/org/scassandra/matchers/Matchers.java b/java-client/src/main/java/org/scassandra/matchers/Matchers.java index 6c4441b1..d67d1672 100644 --- a/java-client/src/main/java/org/scassandra/matchers/Matchers.java +++ b/java-client/src/main/java/org/scassandra/matchers/Matchers.java @@ -15,6 +15,7 @@ */ package org.scassandra.matchers; +import org.scassandra.http.client.BatchExecution; import org.scassandra.http.client.PreparedStatementExecution; import org.scassandra.http.client.Query; @@ -28,4 +29,8 @@ public static PreparedStatementMatcher preparedStatementRecorded(PreparedStateme return new PreparedStatementMatcher(query); } + public static BatchExecutionMatcher batchExecutionRecorded(BatchExecution batchExecution) { + return new BatchExecutionMatcher(batchExecution); + } + } diff --git a/java-client/src/test/java/org/scassandra/http/client/ActivityClientTest.java b/java-client/src/test/java/org/scassandra/http/client/ActivityClientTest.java index b88047f6..29b16361 100644 --- a/java-client/src/test/java/org/scassandra/http/client/ActivityClientTest.java +++ b/java-client/src/test/java/org/scassandra/http/client/ActivityClientTest.java @@ -312,7 +312,7 @@ public void testClearAllActivityHistory() { public void testRetrievalOfBatchExecutions() { //given stubFor(get(urlEqualTo(batchUrl)).willReturn(aResponse() - .withBody("[{\"batchQueries\":[{\"query\":\"select * from people\", \"batchQueryKind\":\"query\", \"variables\": []}],\"consistency\":\"TWO\", \"batchType\":\"COUNTER\"}]"))); + .withBody("[{\"batchQueries\":[{\"query\":\"select * from people\", \"batchQueryKind\":\"query\", \"variables\": [], \"variableTypes\": []}],\"consistency\":\"TWO\", \"batchType\":\"COUNTER\"}]"))); //when List batchExecutions = underTest.retrieveBatches(); //then diff --git a/java-client/src/test/java/org/scassandra/matchers/BatchStatementMatcherTest.java b/java-client/src/test/java/org/scassandra/matchers/BatchStatementMatcherTest.java new file mode 100644 index 00000000..5ee98b05 --- /dev/null +++ b/java-client/src/test/java/org/scassandra/matchers/BatchStatementMatcherTest.java @@ -0,0 +1,802 @@ +/* + * Copyright (C) 2016 Christopher Batey and Dogan Narinc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.scassandra.matchers; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import org.junit.Test; +import org.scassandra.http.client.BatchExecution; +import org.scassandra.http.client.BatchQuery; +import org.scassandra.http.client.BatchType; +import org.scassandra.http.client.Consistency; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.UUID; + +import static com.google.common.collect.Lists.newArrayList; +import static java.util.UUID.fromString; +import static java.util.UUID.randomUUID; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.scassandra.cql.ListType.list; +import static org.scassandra.cql.MapType.map; +import static org.scassandra.cql.PrimitiveType.*; +import static org.scassandra.cql.SetType.set; +import static org.scassandra.http.client.BatchQueryKind.prepared_statement; +import static org.scassandra.http.client.BatchQueryKind.query; +import static org.scassandra.http.client.BatchType.LOGGED; +import static org.scassandra.http.client.BatchType.UNLOGGED; +import static org.scassandra.matchers.Matchers.batchExecutionRecorded; + +public class BatchStatementMatcherTest { + + private final BatchQuery unmatchingBatchQuery = BatchQuery.builder() + .withQuery("some query that does not match") + .withType(query) + .withVariableTypes(SMALL_INT) + .withVariables(1) + .build(); + + private final BatchExecution unmatchingBatchExecution = BatchExecution.builder() + .withBatchQueries(unmatchingBatchQuery) + .withBatchType(BatchType.COUNTER) + .withConsistency(Consistency.ALL.name()) + .build(); + + @Test + public void matchBatchType() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder().withQuery("some query").build(), unmatchingBatchQuery) + .withBatchType(LOGGED) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder().withQuery("some query").build()) + .withBatchType(LOGGED) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void mismatchBatchType() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder().withQuery("some query").build(), unmatchingBatchQuery) + .withBatchType(LOGGED) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder().withQuery("some query").build()) + .withBatchType(UNLOGGED) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertFalse(matched); + } + + @Test + public void matchBatchQueryType() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withType(prepared_statement) + .withQuery("some query") + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withType(prepared_statement) + .withQuery("some query") + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void mismatchingBatchQueryType() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withType(query) + .withQuery("some query") + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withType(prepared_statement) + .withQuery("some query") + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertFalse(matched); + } + + @Test + public void matchWithJustQuery() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder().withQuery("some query").build(), unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder().withQuery("some query").build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void mismatchingConsistency() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder().withQuery("some query").build(), unmatchingBatchQuery) + .withConsistency("QUORUM") + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder().withQuery("some query").build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertFalse(matched); + } + + @Test + public void mismatchingQueryText() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder().withQuery("some peculiar query").build(), unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder().withQuery("some query").build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertFalse(matched); + } + + @Test + public void mismatchingStringVariables() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(TEXT, TEXT, TEXT) + .withVariables("one", "two", "not three!!") + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables("one", "two", "three") + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertFalse(matched); + } + + @Test + public void matchingStringVariables() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(TEXT, ASCII, VARCHAR) + .withVariables("one", "two", "three") + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables("one", "two", "three") + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void matchingNumbers() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(BIG_INT, INT, VAR_INT, INT) + .withVariables(1d, 2d, 3d, 4d) + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables("1", 2, new BigInteger("3"), "4") + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void matchingNonDecimalTypes() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(BIG_INT, COUNTER, INT, VAR_INT) + .withVariables(1d, 2d, 3d, 4d) + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(1, 2, 3, "4") + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void numbersMatchingFloats() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(FLOAT, FLOAT, FLOAT, FLOAT) + .withVariables("1", "2", "3.44", "4.4440") + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables("1", "2", "3.440000", "4.4440") + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void matchingBlobsWithByteBuffer() throws Exception { + //given + byte[] byteArray = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray); + + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(BLOB) + .withVariables("0x0102030405060708090a") + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(byteBuffer) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void numbersMatchingBlobsWhenNotInActualReturnsFalse() throws Exception { + //given + byte[] byteArray = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray); + + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(BLOB) + .withVariables("1.0") + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(byteBuffer) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertFalse(matched); + } + + @Test + public void inetMatching() throws Exception { + //given + InetAddress inetAddress = InetAddress.getLocalHost(); + + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(INET) + .withVariables(inetAddress.getHostAddress()) + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(inetAddress) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void decimalMatchingAsBigDecimal() throws Exception { + //given + BigDecimal decimal = new BigDecimal(90); + + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(DECIMAL) + .withVariables("90") + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(decimal) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void numbersMatchingUUIDsWithUUIDClass() throws Exception { + //given + UUID uuid = randomUUID(); + UUID theSame = fromString(uuid.toString()); + + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(UUID) + .withVariables(theSame.toString()) + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(uuid) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void numbersMatchingUUIDsWithUUIDString() throws Exception { + //given + UUID uuid = randomUUID(); + UUID theSame = fromString(uuid.toString()); + + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(UUID) + .withVariables(theSame.toString()) + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(uuid.toString()) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void matchNonNumberAgainstNumberShouldBeFalse() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(TEXT) + .withVariables("NaN") + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(1.0) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertFalse(matched); + } + + @Test + public void lessVariablesIsFalse() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(INT, INT) + .withVariables(1, 2) + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(1, 2, 3) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertFalse(matched); + } + + @Test + public void moreVariablesIsFalse() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(INT, INT, INT, INT) + .withVariables(1, 2, 3, 4) + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(1, 2, 3) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertFalse(matched); + } + + @Test + public void matchNullAgainstSomethingElseIsFalse() throws Exception { + //given + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(1.0) + .withVariableTypes(INT) + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(new Object[]{null}) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertFalse(matched); + } + + @Test + public void matchesDateWhenSentBackAsLong() throws Exception { + //given + Date date = new Date(); + + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(TIMESTAMP) + .withVariables((double) date.getTime()) + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(date) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowIllegalArgumentIfVariableListNotTheSameAsNumberOfVariables() throws Exception { + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariableTypes(TIMESTAMP, TIMESTAMP) + .withVariables(new Date()) + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(new Date()) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + underTest.matchesSafely(newArrayList(unmatchingBatchExecution, actualExecution)); + } + + @Test + public void matchingTextSets() throws Exception { + //given + Set setOfStrings = Sets.newHashSet("1", "2"); + + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(new ArrayList(setOfStrings)) + .withVariableTypes(set(TEXT)) + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(setOfStrings) + .build()) + .build(); + + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void matchingTextLists() throws Exception { + //given + List listOfText = newArrayList("1", "2"); + + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(new ArrayList(listOfText)) + .withVariableTypes(list(TEXT)) + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(new ArrayList(listOfText)) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } + + @Test + public void matchingTextMaps() throws Exception { + //given + Map mapOfTextText = ImmutableMap.of("one", "1"); + + BatchExecution actualExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(mapOfTextText) + .withVariableTypes(map(TEXT, TEXT)) + .build(), + unmatchingBatchQuery) + .build(); + + BatchExecution expectedExecution = BatchExecution.builder() + .withBatchQueries(BatchQuery.builder() + .withQuery("same query") + .withVariables(mapOfTextText) + .build()) + .build(); + + BatchExecutionMatcher underTest = batchExecutionRecorded(expectedExecution); + + //when + boolean matched = underTest.matchesSafely(newArrayList(actualExecution, unmatchingBatchExecution)); + + //then + assertTrue(matched); + } +} diff --git a/java-it-tests/common/src/main/java/batches/BatchActivityVerificationTest.java b/java-it-tests/common/src/main/java/batches/BatchActivityVerificationTest.java index 237269c0..fa460994 100644 --- a/java-it-tests/common/src/main/java/batches/BatchActivityVerificationTest.java +++ b/java-it-tests/common/src/main/java/batches/BatchActivityVerificationTest.java @@ -28,6 +28,7 @@ import java.util.List; import static org.junit.Assert.assertEquals; +import static org.scassandra.cql.PrimitiveType.VARCHAR; abstract public class BatchActivityVerificationTest extends AbstractScassandraTest { public BatchActivityVerificationTest(CassandraExecutor cassandraExecutor) { @@ -127,7 +128,8 @@ public void preparedStatementsInBatches() { assertEquals(BatchExecution.builder().withBatchQueries( BatchQuery.builder().withQuery("query"), BatchQuery.builder().withQuery("prepared statement ? ?").withVariables("one", "twp") - .withType(BatchQueryKind.prepared_statement)) + .withVariableTypes(VARCHAR, VARCHAR) + .withType(BatchQueryKind.prepared_statement)) .withConsistency("ONE").withBatchType(BatchType.LOGGED).build(), batches.get(0)); } } diff --git a/java-it-tests/common/src/main/java/batches/BatchPrimingTest.java b/java-it-tests/common/src/main/java/batches/BatchPrimingTest.java index 63456dff..31346339 100644 --- a/java-it-tests/common/src/main/java/batches/BatchPrimingTest.java +++ b/java-it-tests/common/src/main/java/batches/BatchPrimingTest.java @@ -191,6 +191,32 @@ public void capturesPreparedStatementVariables() { assertEquals(newArrayList("one", 2.0), queries.get(0).getVariables()); } + @Test + public void capturesPreparedStatementVariableTypes() { + primingClient.prime(PrimingRequest.preparedStatementBuilder() + .withQuery("insert ? ?") + .withThen(then().withVariableTypes(ASCII, INT)) + ); + primingClient.primeBatch( + BatchPrimingRequest.batchPrimingRequest() + .withQueries( + batchQueryPrime("insert ? ?", prepared_statement)) + .withThen(then().withResult(success)) + + ); + + cassandra().executeBatch(newArrayList( + new CassandraQuery("insert ? ?", + PREPARED_STATEMENT, "one", 2) + ), BatchType.LOGGED + ); + + List recordedBatchExecutions = activityClient.retrieveBatches(); + assertEquals(1, recordedBatchExecutions.size()); + List queries = recordedBatchExecutions.get(0).getBatchQueries(); + assertEquals(newArrayList(ASCII, INT), queries.get(0).getVariableTypes()); + } + @Test public void primeBatchWithReadTimeout() { diff --git a/server/src/main/scala/org/scassandra/server/actors/BatchHandler.scala b/server/src/main/scala/org/scassandra/server/actors/BatchHandler.scala index 99de7da5..c8cbabdc 100644 --- a/server/src/main/scala/org/scassandra/server/actors/BatchHandler.scala +++ b/server/src/main/scala/org/scassandra/server/actors/BatchHandler.scala @@ -74,7 +74,7 @@ class BatchHandler(activityLog: ActivityLog, // Decode query parameters using the prepared statement metadata. val dataTypes = prepared.preparedMetadata.columnSpec.map(_.dataType) val values = extractQueryVariables(queryText, Some(byteValues), dataTypes).getOrElse(Nil) - BatchQuery(queryText, BatchQueryKind.Prepared, values) + BatchQuery(queryText, BatchQueryKind.Prepared, values, dataTypes) case None => BatchQuery( "A prepared statement was in the batch but couldn't be found - did you prepare against a different session?", BatchQueryKind.Prepared) diff --git a/server/src/main/scala/org/scassandra/server/priming/ActivityLog.scala b/server/src/main/scala/org/scassandra/server/priming/ActivityLog.scala index 969dd8aa..29702f9c 100644 --- a/server/src/main/scala/org/scassandra/server/priming/ActivityLog.scala +++ b/server/src/main/scala/org/scassandra/server/priming/ActivityLog.scala @@ -110,6 +110,6 @@ class ActivityLog extends LazyLogging { case class Query(query: String, consistency: Consistency, variables: List[Any] = List(), variableTypes: List[DataType] = List()) case class Connection(result: String = "success") case class PreparedStatementExecution(preparedStatementText: String, consistency: Consistency, variables: List[Any], variableTypes: List[DataType]) -case class BatchQuery(query: String, batchQueryKind: BatchQueryKind, variables: List[Any] = List()) +case class BatchQuery(query: String, batchQueryKind: BatchQueryKind, variables: List[Any] = List(), variableTypes: List[DataType] = List()) case class BatchExecution(batchQueries: Seq[BatchQuery], consistency: Consistency, batchType: BatchType) case class PreparedStatementPreparation(preparedStatementText: String) diff --git a/server/src/main/scala/org/scassandra/server/priming/json/PrimingJsonImplicits.scala b/server/src/main/scala/org/scassandra/server/priming/json/PrimingJsonImplicits.scala index 694621fa..f4462c46 100644 --- a/server/src/main/scala/org/scassandra/server/priming/json/PrimingJsonImplicits.scala +++ b/server/src/main/scala/org/scassandra/server/priming/json/PrimingJsonImplicits.scala @@ -210,7 +210,7 @@ object PrimingJsonImplicits extends DefaultJsonProtocol with SprayJsonSupport wi implicit val impPreparedStatementExecution = jsonFormat4(PreparedStatementExecution) implicit val impPreparedStatementPreparation = jsonFormat1(PreparedStatementPreparation) implicit val impVersion = jsonFormat1(Version) - implicit val impBatchQuery = jsonFormat3(BatchQuery) + implicit val impBatchQuery = jsonFormat4(BatchQuery) implicit val impBatchExecution = jsonFormat3(BatchExecution) implicit val impBatchQueryPrime = jsonFormat2(BatchQueryPrime) implicit val impBatchWhen = jsonFormat3(BatchWhen) diff --git a/server/src/test/scala/org/scassandra/server/actors/BatchHandlerTest.scala b/server/src/test/scala/org/scassandra/server/actors/BatchHandlerTest.scala index 1e73d2c8..afbb530d 100644 --- a/server/src/test/scala/org/scassandra/server/actors/BatchHandlerTest.scala +++ b/server/src/test/scala/org/scassandra/server/actors/BatchHandlerTest.scala @@ -22,10 +22,12 @@ import org.mockito.Mockito._ import org.scalatest.mock.MockitoSugar import org.scalatest.{BeforeAndAfter, FunSuite, Matchers} import org.scassandra.codec._ -import org.scassandra.codec.messages.{BatchQueryKind, BatchType, PreparedBatchQuery, SimpleBatchQuery} +import org.scassandra.codec.datatype.DataType +import org.scassandra.codec.messages._ import org.scassandra.server.actors.PrepareHandler.{PreparedStatementQuery, PreparedStatementResponse} import org.scassandra.server.priming._ import org.scassandra.server.priming.batch.PrimeBatchStore +import org.scassandra.server.priming.BatchQuery import org.scassandra.server.priming.prepared.PreparedStoreLookup import scodec.bits.ByteVector @@ -83,14 +85,21 @@ class BatchHandlerTest extends FunSuite with ProtocolActorTest with TestKitBase val id = 1 val idBytes = ByteVector(id) + implicit val protocolVersion = ProtocolVersion.latest + val batch = Batch(BatchType.LOGGED, List( - PreparedBatchQuery(idBytes) + PreparedBatchQuery(idBytes, List(QueryValue(1, DataType.Int).value)) )) underTest ! protocolMessage(batch) prepareHandlerProbe.expectMsg(PreparedStatementQuery(List(id))) - prepareHandlerProbe.reply(PreparedStatementResponse(Map(id -> ("insert into something", Prepared(idBytes))))) + + val prepared = Prepared( + idBytes, + PreparedMetadata(Nil, Some("keyspace"), Some("table"), List(ColumnSpecWithoutTable("0", DataType.Int)))) + + prepareHandlerProbe.reply(PreparedStatementResponse(Map(id -> ("insert into something", prepared)))) expectMsgPF() { case ProtocolResponse(_, VoidResult) => true @@ -98,7 +107,7 @@ class BatchHandlerTest extends FunSuite with ProtocolActorTest with TestKitBase activityLog.retrieveBatchExecutions() should equal(List( BatchExecution(List( - BatchQuery("insert into something", BatchQueryKind.Prepared) + BatchQuery("insert into something", BatchQueryKind.Prepared, List(1), List(DataType.Int)) ), Consistency.ONE, BatchType.LOGGED)) ) }