blob: 0e8e523d0967f547d04252f8f9e03b8554fd770f [file] [log] [blame]
/*
* Class org.apache.derbyTesting.functionTests.tests.lang.TruncateTableTest
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES 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.apache.derbyTesting.functionTests.tests.lang;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.atomic.AtomicBoolean;
import junit.framework.Test;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.DatabasePropertyTestSetup;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.TestConfiguration;
/**
* Tests for TRUNCATE TABLE.
*
*/
public class TruncateTableTest extends BaseJDBCTestCase {
private static final String TEST_DBO = "TEST_DBO";
private static final String RUTH = "RUTH";
private static final String ALICE = "ALICE";
private static final String[] LEGAL_USERS = { TEST_DBO, RUTH, ALICE };
private static final String UNAUTHORIZED_OPERATION = "42507";
public TruncateTableTest(String name) {
super(name);
}
public static Test suite() {
Test cleanTest = TestConfiguration.defaultSuite(TruncateTableTest.class);
Test authenticatedTest = DatabasePropertyTestSetup.builtinAuthentication
( cleanTest, LEGAL_USERS, "" );
Test authorizedTest = TestConfiguration.sqlAuthorizationDecorator( authenticatedTest );
return authorizedTest;
}
/**
* Test that TRUNCATE TABLE works when there is an index on one of the
* columns. Verify that default "CONTINUE IDENTITY" semantics are enforced.
*/
public void testTruncateWithIndex() throws SQLException {
Connection aliceConnection = openUserConnection( ALICE );
Statement st = aliceConnection.createStatement();
ResultSet rs;
String[][] expRS;
//creating a table with one column auto filled with a unique value
st.executeUpdate("create table t1(a int not null generated always as identity primary key, b varchar(100))");
//populate the table
st.executeUpdate("insert into t1(b) values('one'),('two'),('three'),('four'),('five')");
//varify the inserted values
rs = st.executeQuery("select * from t1 order by a");
expRS = new String[][]{
{"1","one"},
{"2","two"},
{"3","three"},
{"4","four"},
{"5","five"}
};
JDBC.assertFullResultSet(rs, expRS);
//executing the truncate table
st.executeUpdate("truncate table t1");
//confirm whether the truncation worked
JDBC.assertEmpty( st.executeQuery( "select * from t1" ) );
//testing whether the truncation work as "CONTINUE IDENTITY"
//semantics are enforced
st.executeUpdate("insert into t1(b) values('six'),('seven')");
rs = st.executeQuery("select * from t1 order by a");
expRS = new String[][]{
{"6","six"},
{"7","seven"}
};
JDBC.assertFullResultSet(rs, expRS);
st.close();
aliceConnection.close();
}
/**
* Test that TRUNCATE TABLE cannot be performed on a table with a
* delete trigger.
*/
public void testTruncateWithDeleteTrigger() throws Exception {
Connection aliceConnection = openUserConnection( ALICE );
Statement s = aliceConnection.createStatement();
// Create two tables, t1 and t2, where deletes from t1 cause inserts
// into t2.
s.execute("create table deltriggertest_t1(x int)");
s.execute("create table deltriggertest_t2(y int)");
s.execute("create trigger deltriggertest_tr after delete on "
+ "deltriggertest_t1 referencing old as old for each row "
+ "insert into deltriggertest_t2 values old.x");
// Prepare a statement that checks the number of rows in the
// destination table (t2).
PreparedStatement checkDest = aliceConnection.prepareStatement(
"select count(*) from deltriggertest_t2");
// Insert rows into t1, delete them, and verify that t2 has grown.
s.execute("insert into deltriggertest_t1 values 1,2,3");
JDBC.assertSingleValueResultSet(checkDest.executeQuery(), "0");
assertUpdateCount(s, 3, "delete from deltriggertest_t1");
JDBC.assertSingleValueResultSet(checkDest.executeQuery(), "3");
// Now do the same with TRUNCATE instead of DELETE. Expect it to fail
// because there is a delete trigger on the table.
s.execute("insert into deltriggertest_t1 values 4,5");
assertStatementError("XCL49", s, "truncate table deltriggertest_t1");
JDBC.assertSingleValueResultSet(checkDest.executeQuery(), "3");
}
/**
* Test that TRUNCATE TABLE isn't allowed on a table referenced by a
* foreign key constraint on another table.
*/
public void testTruncateWithForeignKey() throws SQLException {
Connection aliceConnection = openUserConnection( ALICE );
Statement s = aliceConnection.createStatement();
// Create two tables with a foreign key relationship.
s.execute("create table foreignkey_t1(x int primary key)");
s.execute("create table foreignkey_t2(y int references foreignkey_t1)");
s.execute("insert into foreignkey_t1 values 1,2");
s.execute("insert into foreignkey_t2 values 2");
// Truncating the referenced table isn't allowed as that would
// break referential integrity.
assertStatementError("XCL48", s, "truncate table foreignkey_t1");
// Truncating the referencing table is OK.
s.execute("truncate table foreignkey_t2");
JDBC.assertEmpty( s.executeQuery( "select * from foreignkey_t2" ) );
}
/**
* Test that TRUNCATE TABLE is allowed on a referenced table if it's only
* referenced by itself.
*/
public void testSelfReferencing() throws SQLException {
Connection aliceConnection = openUserConnection( ALICE );
Statement s = aliceConnection.createStatement();
// Workaround for DERBY-5139: If this test case happens to be running
// first, before the schema ALICE has been created, the CREATE TABLE
// statement below will fail. Normally, CREATE TABLE should create the
// ALICE schema automatically, but for some reason that doesn't happen
// when creating a self-referencing table. Create the schema manually
// for now, if it doesn't already exist.
try {
s.execute("CREATE SCHEMA ALICE");
} catch (SQLException sqle) {
// It's OK to fail if schema already exists.
assertSQLState("X0Y68", sqle);
}
s.execute("create table self_referencing_t1(x int primary key, "
+ "y int references self_referencing_t1)");
s.execute("insert into self_referencing_t1 values (1, null), (2, 1)");
s.execute("truncate table self_referencing_t1");
JDBC.assertEmpty( s.executeQuery( "select * from self_referencing_t1" ) );
}
/**
* Test that dbo and owner can truncate table but no-one else can.
*/
public void testPerms() throws Exception
{
Connection dboConnection = openUserConnection( TEST_DBO );
Connection aliceConnection = openUserConnection( ALICE );
Connection ruthConnection = openUserConnection( RUTH );
Statement dboStatement = dboConnection.createStatement();
Statement aliceStatement = aliceConnection.createStatement();
Statement ruthStatement = ruthConnection.createStatement();
// user can truncate her own table
aliceStatement.execute( "create table t_perm( a int )" );
aliceStatement.execute( "grant delete on t_perm to public" );
aliceStatement.execute( "grant select on t_perm to public" );
aliceStatement.execute( "insert into t_perm( a ) values ( 1 )" );
aliceStatement.execute( "truncate table t_perm" );
JDBC.assertEmpty( aliceStatement.executeQuery( "select * from t_perm" ) );
// ordinary other user can't truncate table
aliceStatement.execute( "insert into t_perm( a ) values ( 2 )" );
assertStatementError( UNAUTHORIZED_OPERATION, ruthStatement, "truncate table alice.t_perm" );
JDBC.assertFullResultSet
(
ruthStatement.executeQuery( "select * from alice.t_perm" ),
new String[][] { { "2" } }
);
// even though they are authorized to delete from the table
ruthStatement.execute( "delete from alice.t_perm" );
JDBC.assertEmpty( ruthStatement.executeQuery( "select * from alice.t_perm" ) );
// the dbo, however, can truncate the table
aliceStatement.execute( "insert into t_perm( a ) values ( 3 )" );
JDBC.assertFullResultSet
(
aliceStatement.executeQuery( "select * from alice.t_perm" ),
new String[][] { { "3" } }
);
dboStatement.execute( "truncate table alice.t_perm" );
JDBC.assertEmpty( dboStatement.executeQuery( "select * from alice.t_perm" ) );
// tidy up
dboStatement.close();
aliceStatement.close();
ruthStatement.close();
dboConnection.close();
aliceConnection.close();
ruthConnection.close();
}
/**
* Test that TRUNCATE TABLE and DROP TABLE do not cause held cursors
* to trip across an NPE. See DERBY-268.
*/
public void testCursor() throws Exception
{
Connection cursorConnection = openUserConnection( ALICE );
Connection truncatorConnection = openUserConnection( ALICE );
cursorConnection.setAutoCommit( false );
truncatorConnection.setAutoCommit( false );
cursorMinion( cursorConnection, truncatorConnection, "truncateTab", "truncate table " );
cursorMinion( cursorConnection, truncatorConnection, "dropTab", "drop table " );
cursorConnection.close();
}
private void cursorMinion
( Connection cursorConnection, Connection truncatorConnection, String tableName, String truncationStub )
throws Exception
{
Statement ddlStatement = cursorConnection.createStatement();
Statement truncatorStatement = truncatorConnection.createStatement();
ddlStatement.execute( "create table " + tableName + "( a int )" );
ddlStatement.execute( "insert into " + tableName + "( a ) values ( 1 ), ( 2 )" );
ddlStatement.close();
cursorConnection.commit();
Statement cursorStatement = cursorConnection.createStatement
(
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY,
ResultSet.HOLD_CURSORS_OVER_COMMIT
);
ResultSet cursor = cursorStatement.executeQuery( "select * from " + tableName );
// read first row, then commit the holdable cursor
cursor.next();
assertEquals( 1, cursor.getInt( 1 ) );
cursorConnection.commit();
// now truncate the table and commit
truncatorStatement.execute( truncationStub + tableName );
truncatorConnection.commit();
// we expect to be able to finish draining the cursor
cursor.next();
assertEquals( 2, cursor.getInt( 1 ) );
// and we expect to be told that the cursor is drained. this is
// where the NPE was raised
assertFalse( cursor.next() );
cursor.close();
cursorConnection.commit();
cursorStatement.close();
truncatorStatement.close();
}
/**
* Test that statement invalidation works when TRUNCATE TABLE statements
* and other statements accessing the same table execute concurrently.
* DERBY-4275.
*/
public void testConcurrentInvalidation() throws Exception {
Statement s = createStatement();
s.execute("create table d4275(x int)");
// Object used by the main thread to tell the helper thread to stop.
// The helper thread stops once the value is set to true.
final AtomicBoolean stop = new AtomicBoolean();
// Holder for anything thrown by the run() method in the helper thread.
final Throwable[] error = new Throwable[1];
// Set up a helper thread that executes a query against the table
// until the main thread tells it to stop.
Connection c2 = openDefaultConnection();
final PreparedStatement ps = c2.prepareStatement("select * from d4275");
Thread t = new Thread() {
public void run() {
try {
while (!stop.get()) {
JDBC.assertEmpty(ps.executeQuery());
}
} catch (Throwable t) {
error[0] = t;
}
}
};
t.start();
// Truncate the table while a query is being executed against the
// same table to force invalidation of the running statement. Since
// the problem we try to reproduce is timing-dependent, do it 100
// times to increase the chance of hitting the bug.
try {
for (int i = 0; i < 100; i++) {
s.execute("truncate table d4275");
}
} finally {
// We're done, so tell the helper thread to stop.
stop.set(true);
}
t.join();
// Before DERBY-4275, the helper thread used to fail with an error
// saying the container was not found.
if (error[0] != null) {
fail("Helper thread failed", error[0]);
}
// Cleanup.
ps.close();
c2.close();
}
}