Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,11 @@ public static void setup() throws IOException, InterruptedException, ExecutionEx
* @return The newly opened JDBC connection.
*/
public CloudSpannerJdbcConnection createConnection() throws SQLException {
StringBuilder url = new StringBuilder("jdbc:cloudspanner:");
String host = env.getTestHelper().getOptions().getHost();
if (host != null) {
url.append(host.substring(host.indexOf(':') + 1));
}
url.append("/").append(getDatabase().getId().getName());
// Create a connection URL for the generic connection API.
StringBuilder url =
ITAbstractSpannerTest.extractConnectionUrl(env.getTestHelper().getOptions(), getDatabase());
// Prepend it with 'jdbc:' to make it a valid JDBC connection URL.
url.insert(0, "jdbc:");
if (hasValidKeyFile()) {
url.append(";credentials=").append(getKeyFile());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.google.cloud.spanner.IntegrationTestEnv;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.TransactionManager;
import com.google.cloud.spanner.TransactionManager.TransactionState;
Expand Down Expand Up @@ -167,6 +168,20 @@ protected static Database getDatabase() {
return database;
}

/**
* Returns a connection URL that is extracted from the given {@link SpannerOptions} and database
* in the form
* cloudspanner:[//host]/projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID
*/
static StringBuilder extractConnectionUrl(SpannerOptions options, Database database) {
StringBuilder url = new StringBuilder("cloudspanner:");
if (options.getHost() != null) {
url.append(options.getHost().substring(options.getHost().indexOf(':') + 1));
}
url.append("/").append(database.getId().getName());
return url;
}

@BeforeClass
public static void setup() throws IOException, InterruptedException, ExecutionException {
database = env.getTestHelper().createTestDatabase();
Expand Down Expand Up @@ -211,7 +226,8 @@ public ITConnection createConnection(
public ITConnection createConnection(
List<StatementExecutionInterceptor> interceptors,
List<TransactionRetryListener> transactionRetryListeners) {
StringBuilder url = new StringBuilder("cloudspanner:/").append(getDatabase().getId().getName());
StringBuilder url =
extractConnectionUrl(getTestEnv().getTestHelper().getOptions(), getDatabase());
appendConnectionUri(url);
ConnectionOptions.Builder builder =
ConnectionOptions.newBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.spanner.jdbc.it;

import com.google.cloud.spanner.IntegrationTest;
import com.google.cloud.spanner.jdbc.ITAbstractJdbcTest;
import com.google.cloud.spanner.jdbc.JdbcSqlScriptVerifier;
import com.google.cloud.spanner.jdbc.JdbcSqlScriptVerifier.JdbcGenericConnection;
import com.google.cloud.spanner.jdbc.SqlScriptVerifier;
import java.sql.Connection;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@Category(IntegrationTest.class)
@RunWith(JUnit4.class)
public class ITJdbcSqlMusicScriptTest extends ITAbstractJdbcTest {
private static final String SCRIPT_FILE = "ITSqlMusicScriptTest.sql";

@Test
public void testRunScript() throws Exception {
JdbcSqlScriptVerifier verifier = new JdbcSqlScriptVerifier();
try (Connection connection = createConnection()) {
verifier.verifyStatementsInFile(
JdbcGenericConnection.of(connection), SCRIPT_FILE, SqlScriptVerifier.class, true);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.spanner.jdbc.it;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import com.google.cloud.spanner.AbortedDueToConcurrentModificationException;
import com.google.cloud.spanner.IntegrationTest;
import com.google.cloud.spanner.Mutation;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.jdbc.AbstractSqlScriptVerifier.GenericConnection;
import com.google.cloud.spanner.jdbc.ITAbstractSpannerTest;
import com.google.cloud.spanner.jdbc.SqlScriptVerifier;
import com.google.cloud.spanner.jdbc.SqlScriptVerifier.SpannerGenericConnection;
import java.util.ArrayList;
import java.util.List;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.junit.runners.MethodSorters;

/**
* Integration test that runs one long sql script using the default Singers/Albums/Songs/Concerts
* data model
*/
@Category(IntegrationTest.class)
@RunWith(JUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ITSqlMusicScriptTest extends ITAbstractSpannerTest {
private static final String SCRIPT_FILE = "ITSqlMusicScriptTest.sql";

@Test
public void test01_RunScript() throws Exception {
SqlScriptVerifier verifier = new SqlScriptVerifier();
try (GenericConnection connection = SpannerGenericConnection.of(createConnection())) {
verifier.verifyStatementsInFile(connection, SCRIPT_FILE, SqlScriptVerifier.class, true);
}
}

@Test
public void test02_RunAbortedTest() {
final long SINGER_ID = 2L;
final long VENUE_ID = 68L;
final long NUMBER_OF_SINGERS = 30L;
final long NUMBER_OF_ALBUMS = 60L;
final long NUMBER_OF_SONGS = 149L;
final long NUMBER_OF_CONCERTS = 100L;
long numberOfSongs = 0L;
AbortInterceptor interceptor = new AbortInterceptor(0.0D);
try (ITConnection connection = createConnection(interceptor)) {
connection.setAutocommit(false);
connection.setRetryAbortsInternally(true);
// Read all data from the different music tables in the transaction
// The previous test deleted the first two Singers records.
long expectedId = 3L;
try (ResultSet rs =
connection.executeQuery(Statement.of("SELECT * FROM Singers ORDER BY SingerId"))) {
while (rs.next()) {
assertThat(rs.getLong("SingerId"), is(equalTo(expectedId)));
expectedId++;
}
}
assertThat(expectedId, is(equalTo(NUMBER_OF_SINGERS + 1L)));
expectedId = 3L;
try (ResultSet rs =
connection.executeQuery(Statement.of("SELECT * FROM Albums ORDER BY AlbumId"))) {
while (rs.next()) {
assertThat(rs.getLong("AlbumId"), is(equalTo(expectedId)));
expectedId++;
// 31 and 32 were deleted by the first test script.
if (expectedId == 31L || expectedId == 32L) {
expectedId = 33L;
}
}
}
assertThat(expectedId, is(equalTo(NUMBER_OF_ALBUMS + 1L)));
expectedId = 1L;
try (ResultSet rs =
connection.executeQuery(Statement.of("SELECT * FROM Songs ORDER BY TrackId"))) {
while (rs.next()) {
assertThat(rs.getLong("TrackId"), is(equalTo(expectedId)));
expectedId++;
numberOfSongs++;
// 40, 64, 76, 86 and 96 were deleted by the first test script.
if (expectedId == 40L
|| expectedId == 64L
|| expectedId == 76L
|| expectedId == 86L
|| expectedId == 96L) {
expectedId++;
}
}
}
assertThat(expectedId, is(equalTo(NUMBER_OF_SONGS + 1L)));
// Concerts are not in the table hierarchy, so no records have been deleted.
expectedId = 1L;
try (ResultSet rs =
connection.executeQuery(Statement.of("SELECT * FROM Concerts ORDER BY VenueId"))) {
while (rs.next()) {
assertThat(rs.getLong("VenueId"), is(equalTo(expectedId)));
expectedId++;
}
}
assertThat(expectedId, is(equalTo(NUMBER_OF_CONCERTS + 1L)));

// make one small concurrent change in a different transaction
List<Long> originalPrices;
List<Long> newPrices;
try (ITConnection connection2 = createConnection()) {
assertThat(connection2.isAutocommit(), is(true));
try (ResultSet rs =
connection2.executeQuery(
Statement.newBuilder(
"SELECT TicketPrices FROM Concerts WHERE SingerId=@singer AND VenueId=@venue")
.bind("singer")
.to(SINGER_ID)
.bind("venue")
.to(VENUE_ID)
.build())) {
assertThat(rs.next(), is(true));
originalPrices = rs.getLongList(0);
// increase one of the prices by 1
newPrices = new ArrayList<>(originalPrices);
newPrices.set(1, originalPrices.get(1) + 1);
connection2.executeUpdate(
Statement.newBuilder(
"UPDATE Concerts SET TicketPrices=@prices WHERE SingerId=@singer AND VenueId=@venue")
.bind("prices")
.toInt64Array(newPrices)
.bind("singer")
.to(SINGER_ID)
.bind("venue")
.to(VENUE_ID)
.build());
}
}

// try to add a new song and then try to commit, but trigger an abort on commit
connection.bufferedWrite(
Mutation.newInsertBuilder("Songs")
.set("SingerId")
.to(3L)
.set("AlbumId")
.to(3L)
.set("TrackId")
.to(1L)
.set("SongName")
.to("Aborted")
.set("Duration")
.to(1L)
.set("SongGenre")
.to("Unknown")
.build());
interceptor.setProbability(1.0);
interceptor.setOnlyInjectOnce(true);
// the transaction retry should fail because of the concurrent modification
boolean expectedException = false;
try {
connection.commit();
} catch (AbortedDueToConcurrentModificationException e) {
expectedException = true;
}
// verify that the commit aborted, an internal retry was started and then aborted because of
// the concurrent modification
assertThat(expectedException, is(true));
// verify that the prices were changed
try (ResultSet rs =
connection.executeQuery(
Statement.newBuilder(
"SELECT TicketPrices FROM Concerts WHERE SingerId=@singer AND VenueId=@venue")
.bind("singer")
.to(SINGER_ID)
.bind("venue")
.to(VENUE_ID)
.build())) {
assertThat(rs.next(), is(true));
assertThat(rs.getLongList(0), is(equalTo(newPrices)));
}
// verify that the new song was not written to the database
try (ResultSet rs = connection.executeQuery(Statement.of("SELECT COUNT(*) FROM Songs"))) {
assertThat(rs.next(), is(true));
assertThat(rs.getLong(0), is(equalTo(numberOfSongs)));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1562,14 +1562,6 @@ private void assertRetryStatistics(
assertThat(
RETRY_STATISTICS.totalConcurrentModifications,
is(equalTo(concurrentModificationsExpected)));
// There might be more retry attempts than expected. The number of successful retries should be
// equal to the actual difference between started and successful.
assertThat(
RETRY_STATISTICS.totalSuccessfulRetries,
is(
equalTo(
RETRY_STATISTICS.totalRetryAttemptsStarted
- minAttemptsStartedExpected
+ successfulRetriesExpected)));
assertThat(RETRY_STATISTICS.totalSuccessfulRetries >= successfulRetriesExpected, is(true));
}
}
Loading