Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction is not rollbacked when first statement fails #123

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/org/mariadb/jdbc/MariaDbConnection.java
Expand Up @@ -717,7 +717,7 @@ public void rollback() throws SQLException {
if (!getAutoCommit()) {
lock.lock();
try {
if (!getAutoCommit() && protocol.inTransaction()) {
if (!getAutoCommit()) {
try (Statement st = createStatement()) {
st.execute("ROLLBACK");
}
Expand Down
141 changes: 141 additions & 0 deletions src/test/java/org/mariadb/jdbc/TransactionTest.java
@@ -0,0 +1,141 @@
/*
*
* MariaDB Client for Java
*
* Copyright (c) 2012-2014 Monty Program Ab.
* Copyright (c) 2015-2017 MariaDB Ab.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with this library; if not, write to Monty Program Ab info@montyprogram.com.
*
* This particular MariaDB Client for Java file is work
* derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
* the following copyright and notice provisions:
*
* Copyright (c) 2009-2011, Marcus Eriksson
*
* 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 the driver nor the names of its contributors may not 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 HOLDER 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 org.mariadb.jdbc;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class TransactionTest extends BaseTest {

/**
* Tables initialisation.
*
* @throws SQLException
* exception
*/
@BeforeClass()
public static void initClass() throws SQLException {
Statement stmt = sharedConnection.createStatement();
stmt.execute("drop table if exists tx_fore_key");
stmt.execute("drop table if exists tx_prim_key");
createTable("tx_prim_key", "id int not null primary key", "engine=innodb");
createTable("tx_fore_key",
"id int not null primary key, id_ref int not null, "
+ "foreign key (id_ref) references tx_prim_key(id) on delete restrict on update restrict",
"engine=innodb");
}

/**
* Clean up created tables.
*
* @throws SQLException
* exception
*/
@AfterClass
public static void afterClass() throws SQLException {
Statement stmt = sharedConnection.createStatement();
stmt.execute("drop table if exists tx_fore_key");
stmt.execute("drop table if exists tx_prim_key");
}

/*
* Test with different APIs that generated keys work. Also test that any name in
* generatedKeys.getXXX(String name) can be passed and is equivalent to
* generatedKeys.getXXX(1). This might not be 100% compliant, but is a simple
* and effective solution for MySQL that does not does not support more than a
* single autogenerated value.
*/
@Test
public void testProperRollback() throws Exception {
// 1. setup test data
try (Statement st = sharedConnection.createStatement()) {
st.executeUpdate("insert into tx_prim_key(id) values(32)");
st.executeUpdate("insert into tx_fore_key(id, id_ref) values(42, 32)");
}

// 2. try to delete entry in Primary table in a transaction - wich will fail due
// foreign key.
sharedConnection.setAutoCommit(false);
try (Statement st = sharedConnection.createStatement()) {
st.executeUpdate("delete from tx_prim_key where id = 32");
sharedConnection.commit();
fail("Expected SQLException");
} catch (SQLException e) {
// This exception is expected
assertTrue(e.getMessage().contains("a foreign key constraint fails"));
// expected exception: Cannot delete or update a parent row: a foreign key
// constraint fails
sharedConnection.rollback(); // <-- will not properly roll back the transaction
// st.execute("ROLLBACK"); <-- will fix the problem
}

// 3. now try to delete it in a second connection. Note that there is an open
// transaction
// from attempt #2 which locks the table. Put breakpoint here and execute
// "SELECT * FROM information_schema.INNODB_TRX" to see the transaction
try (Connection conn2 = openNewConnection(connUri); Statement st = conn2.createStatement()) {
st.setQueryTimeout(3);
st.executeUpdate("delete from tx_fore_key where id = 42");
st.executeUpdate("delete from tx_prim_key where id = 32");
}
}

}