blob: 952a505e36914cfdaf2219b8bdc19e1c958348ca [file] [log] [blame]
/*
*
* Derby - Class SURQueryMixTest
*
* 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.jdbcapi;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import junit.extensions.TestSetup;
import junit.framework.Test;
import org.apache.derbyTesting.junit.BaseTestSuite;
import org.apache.derbyTesting.junit.TestConfiguration;
/**
* Tests for Scrollable Updatable ResultSet (SUR). This TestCase tests
* scrolling (navigation), updates (using updateXXX() and updateRow() or
* positioned updates), deletion of records (using deleteRow() or positioned
* deletes) of ResultSets.
*/
public class SURQueryMixTest extends SURBaseTest
{
/**
* Constructor
* @param model name of data model for this TestCase
* @param query to use for producing the resultset
* @param cursorName name of cursor
* @param positioned flag to determine if the Test should use positioned
* updates/deletes instead of updateRow() and deleteRow()
*/
public SURQueryMixTest(final String model, final String query,
final String cursorName, final boolean positioned)
{
super("SURQueryMixTest{Model=" + model + ",Query=" +query + ",Cursor="
+ cursorName + ",Positioned=" + positioned + "}");
this.query = query;
this.cursorName = cursorName;
this.positioned = positioned;
this.checkRowUpdated = false;
this.checkRowDeleted = false;
}
/**
* Test SUR properties of the query
*/
public void runTest()
throws SQLException
{
println(query);
DatabaseMetaData dbMeta = getConnection().getMetaData();
if (dbMeta.ownDeletesAreVisible(ResultSet.TYPE_SCROLL_INSENSITIVE)) {
checkRowDeleted = true;
}
Statement s = createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
s.setCursorName(cursorName);
ResultSet rs = s.executeQuery(query);
checkRowUpdated = dbMeta.ownUpdatesAreVisible(rs.getType());
checkRowDeleted = dbMeta.ownDeletesAreVisible(rs.getType());
// Create map with rows
Map<Integer, String> rows = createRowMap(rs);
// Set of rows which are updated (contains Integer with position in RS)
final Set<Integer> updatedRows = new HashSet<Integer>();
// Set of rows which are deleted (contains Integer with position in RS)
final Set<Integer> deletedRows = new HashSet<Integer>();
// Test navigation
testNavigation(rs, rows, updatedRows, deletedRows);
// Only test updatability if the ResultSet is updatable:
// (Note: this enables the test do run successfully even if
// scrollable updatable resultsets are not implemented.
// If SUR is not implemented, a well behaved JDBC driver will
// downgrade the concurrency mode to READ_ONLY).
// SUR may be implemented incrementally, i.e first in embedded mode
// then in the network driver.)
if (rs.getConcurrency()==ResultSet.CONCUR_UPDATABLE) {
// update a random sample of 2 records
updateRandomSampleOfNRecords(rs, rows, updatedRows, 2);
testNavigation(rs, rows, updatedRows, deletedRows);
// update a random sample of 5 records
updateRandomSampleOfNRecords(rs, rows, updatedRows, 5);
testNavigation(rs, rows, updatedRows, deletedRows);
// update a random sample of 10 records
updateRandomSampleOfNRecords(rs, rows, updatedRows, 10);
testNavigation(rs, rows, updatedRows, deletedRows);
// delete a random sample of 2 records
deleteRandomSampleOfNRecords(rs, rows, deletedRows, 2);
testNavigation(rs, rows, updatedRows, deletedRows);
// delete a random sample of 5 records
deleteRandomSampleOfNRecords(rs, rows, deletedRows, 5);
testNavigation(rs, rows, updatedRows, deletedRows);
// delete a random sample of 10 records
deleteRandomSampleOfNRecords(rs, rows, deletedRows, 10);
testNavigation(rs, rows, updatedRows, deletedRows);
} else {
assertTrue("ResultSet concurrency downgraded to CONCUR_READ_ONLY",
false);
}
rs.close();
s.close();
}
/**
* Creates a Map of the values in the ResultSet.
* The key object in the map, is the postion in the
* ResultSet (Integer 1..n), while the value is a
* concatenation of the strings for all columns in the row.
*/
private Map<Integer, String> createRowMap(final ResultSet rs)
throws SQLException
{
final Map<Integer, String> rows = new HashMap<Integer, String>();
rs.beforeFirst();
assertTrue("Unexpected return from isBeforeFirst()",
rs.isBeforeFirst());
int i = 0;
int sum = 0;
int expectedSum = 0;
boolean checkSum = true;
while (rs.next()) {
expectedSum += i;
i++;
String row = getRowString(rs);
println(row);
rows.put(i, row);
sum += rs.getInt(1);
if (rs.getInt(1) < 0) {
checkSum = false;
}
}
if (i<SURDataModelSetup.recordCount) {
checkSum = false;
}
assertTrue("Unexpected return from isAfterLast()", rs.isAfterLast());
if (checkSum) {
assertEquals("Sum for column 1 is not correct", expectedSum, sum);
}
return rows;
}
/**
* Create a random sample of rows
* @param rows Map to create sample from
* @param k number of rows in the sample
* @return a list containing k elements of rows
**/
private List createRandomSample(final Map<Integer, String> rows, int k) {
Random r = new Random();
ArrayList<Integer> sampledKeys = new ArrayList<Integer>();
int n = 0;
for (Integer key : rows.keySet()) {
n++;
if (n<=k) {
sampledKeys.add(key);
} else {
// sampledKeys now has a size of k
double d = r.nextDouble();
// p = probability of going into the sample
double p = (double) k / (double) n;
if (d<p) {
// Replace a random value from the sample with the new value
int keyToReplace = r.nextInt(k);
sampledKeys.set(keyToReplace, key);
}
}
}
return sampledKeys;
}
/**
* Delete a random sample of n records in the resultset
* @param rs result set to be updated
* @param rows map of rows, will also be updated
* @param deletedRows set of rows being deleted (position in RS)
* @param k number of records to be deleted
*/
private void deleteRandomSampleOfNRecords(final ResultSet rs,
final Map<Integer, String> rows,
final Set<Integer> deletedRows,
final int k)
throws SQLException
{
List sampledKeys = createRandomSample(rows, k);
println("Sampled keys:" + sampledKeys);
ResultSetMetaData meta = rs.getMetaData();
for (Iterator i = sampledKeys.iterator(); i.hasNext();) {
Integer key = (Integer) i.next();
rs.absolute(key.intValue());
if (rs.rowDeleted()) continue; // skip deleting row if already deleted
if (positioned) {
createStatement().executeUpdate
("DELETE FROM T1 WHERE CURRENT OF \"" + cursorName +
"\"");
} else {
rs.deleteRow();
}
rs.relative(0);
println("Deleted row " + key);
// Update the rows table
rows.put(key, getRowString(rs));
// Update the updatedRows set
deletedRows.add(key);
}
}
/**
* Update a random sample of n records in the resultset
* @param rs result set to be updated
* @param rows map of rows, will also be updated
* @param updatedRows set of being updated (position in RS)
* @param k number of records to be updated
*/
private void updateRandomSampleOfNRecords(final ResultSet rs,
final Map<Integer, String> rows,
final Set<Integer> updatedRows,
final int k)
throws SQLException
{
List sampledKeys = createRandomSample(rows, k);
println("Sampled keys:" + sampledKeys);
ResultSetMetaData meta = rs.getMetaData();
for (Iterator i = sampledKeys.iterator(); i.hasNext();) {
Integer key = (Integer) i.next();
rs.absolute(key.intValue());
if (positioned) {
updatePositioned(rs, meta);
rs.relative(0); // If this call is not here, the old values are
// returned in rs.getXXX calls
} else {
updateRow(rs, meta);
}
// Update the rows table
rows.put(key, getRowString(rs));
// Update the updatedRows set
updatedRows.add(key);
}
}
/**
* Updates the current row in the ResultSet using updateRow()
* @param rs ResultSet to be updated
* @param meta meta for the ResultSet
**/
private void updateRow(final ResultSet rs, final ResultSetMetaData meta)
throws SQLException
{
for (int column = 1; column<=meta.getColumnCount(); column++) {
if (meta.getColumnType(column)==Types.INTEGER) {
// Set to negative value
rs.updateInt(column, -rs.getInt(column));
} else {
rs.updateString(column, "UPDATED_" + rs.getString(column));
}
}
rs.updateRow();
}
/**
* Updates the current row in the ResultSet using updateRow()
* @param rs ResultSet to be updated
* @param meta meta for the ResultSet
**/
private void updatePositioned(final ResultSet rs,
final ResultSetMetaData meta)
throws SQLException
{
StringBuffer sb = new StringBuffer();
sb.append("UPDATE T1 SET ");
for (int column = 1; column<=meta.getColumnCount(); column++) {
sb.append(meta.getColumnName(column));
sb.append("=?");
if (column<meta.getColumnCount()) {
sb.append(",");
}
}
sb.append(" WHERE CURRENT OF \"");
sb.append(cursorName);
sb.append("\"");
println(sb.toString());
PreparedStatement ps = prepareStatement(sb.toString());
for (int column = 1; column<=meta.getColumnCount(); column++) {
if (meta.getColumnType(column)==Types.INTEGER) {
// Set to negative value
ps.setInt(column, -rs.getInt(column));
} else {
ps.setString(column, "UPDATED_" + rs.getString(column));
}
}
assertEquals("Expected one row to be updated", 1, ps.executeUpdate());
}
/**
* Tests navigation in ResultSet.
* @param rs ResultSet to test navigation of.
* Needs to be scrollable
* @param rows a sample of the rows which are in the ResultSet. Maps
* position to a concatenation of the string values
* @param updatedRows a integer set of which rows that have been
* updated. Used to test rowUpdated()
* @param deletedRows a integer set of which rows that have been
* deleted. Used to test rowDeleted()
*/
private void testNavigation(final ResultSet rs, final Map rows,
final Set updatedRows, final Set deletedRows)
throws SQLException
{
rs.afterLast();
{
int i = rows.size();
while (rs.previous()) {
String rowString = getRowString(rs);
assertEquals("Navigating with rs.previous(). The row is " +
"different compared to the value when navigating " +
"forward.", rows.get(i), rowString);
if (checkRowUpdated && updatedRows.contains(i)) {
assertTrue("Expected rs.rowUpdated() to return true on " +
"updated row " + rowString, rs.rowUpdated());
}
if (checkRowDeleted && deletedRows.contains(i)) {
assertTrue("Expected rs.rowDeleted() to return true on " +
"deleted row " + rowString, rs.rowDeleted());
}
i--;
}
}
// Test absolute
for (int i = 1; i <= rows.size(); i++) {
assertTrue("Unexpected return from absolute()", rs.absolute(i));
String rowString = getRowString(rs);
assertEquals("Navigating with rs.absolute(). The row is " +
"different compared to the value" +
" when navigating forward.",
rows.get(i),
rowString);
if (checkRowUpdated && updatedRows.contains(i)) {
assertTrue("Expected rs.rowUpdated() to return true on " +
"updated row " + rowString, rs.rowUpdated());
}
if (checkRowDeleted && deletedRows.contains(i)) {
assertTrue("Expected rs.rowDeleted() to return true on " +
"deleted row " + rowString, rs.rowDeleted());
}
}
assertFalse("Unexpected return from absolute()", rs.absolute(0));
assertTrue("Unexpected return from isBeforeFirst()",
rs.isBeforeFirst());
assertFalse("Unexpected return from absolute()",
rs.absolute(rows.size() + 1));
assertTrue("Unexpected return from isAfterLast()", rs.isAfterLast());
assertTrue("Unexpected return from absolute()", rs.absolute(-1));
assertTrue("Unexpected return from isLast()", rs.isLast());
assertTrue("Unexpected return from absolute()", rs.absolute(1));
assertTrue("Unexpected return from isFirst()", rs.isFirst());
// Test relative
{
rs.beforeFirst();
assertTrue("Unexptected return from isBeforeFirst()",
rs.isBeforeFirst());
int relativePos = rows.size();
assertTrue("Unexpected return from relative()",
rs.relative(relativePos));
// Should now be on the last row
assertTrue("Unexptected return from isLast()", rs.isLast());
assertEquals("Navigating with rs.relative(+). " +
"A tuple was different compared to the value" +
" when navigating forward.",
rows.get(relativePos),
getRowString(rs));
assertTrue("Unexpected return from relative()",
rs.relative((-relativePos + 1)));
// Should now be on the first row
assertTrue("Unexptected return from isFirst()", rs.isFirst());
assertEquals("Navigating with rs.relative(-). " +
"A tuple was different compared to the value" +
" when navigating forward.",
rows.get(1),
getRowString(rs));
}
// Test navigation in the end of the ResultSet
rs.afterLast();
assertTrue("Unexpected return from isAfterLast()", rs.isAfterLast());
assertTrue("Unexpected return from previous()", rs.previous());
assertTrue("Unexpected return from isLast()", rs.isLast());
assertFalse("Unexpected return from next()", rs.next());
assertTrue("Unexpected return from isAfterLast()", rs.isAfterLast());
rs.last();
assertTrue("Unexpected return from isLast()", rs.isLast());
assertFalse("Unexpected return from next()", rs.next());
assertTrue("Unexpected return from isAfterLast()", rs.isAfterLast());
// Test navigation in the beginning of the ResultSet
rs.beforeFirst();
assertTrue("Unexpected return from isBeforeFirst()",
rs.isBeforeFirst());
assertTrue("Unexpected return from next()", rs.next());
assertTrue("Unexpected return from isFirst", rs.isFirst());
assertFalse("Unexpected return from previous()", rs.previous());
assertTrue("Unexpected return from isBeforeFirst()",
rs.isBeforeFirst());
rs.first();
assertTrue("Unexpected return from isFirst", rs.isFirst());
assertFalse("Unexpected return from previous()", rs.previous());
assertTrue("Unexpected return from isBeforeFirst()",
rs.isBeforeFirst());
}
/**
* Get a concatenation of the values of the
* current Row in the ResultSet
*/
private String getRowString(final ResultSet rs)
throws SQLException
{
int numberOfColumns = rs.getMetaData().getColumnCount();
StringBuffer sb = new StringBuffer();
if (rs.rowDeleted()) return "";
for (int i = 1; i <= numberOfColumns; i++) {
sb.append(rs.getString(i));
if (i < numberOfColumns) {
sb.append(',');
}
}
return sb.toString();
}
private final String query;
private final String cursorName;
private final boolean positioned;
private boolean checkRowUpdated;
private boolean checkRowDeleted;
private final static String[] selectConditions = new String[] {
"WHERE c like 'T%'",
" ",
"WHERE b > 5",
"WHERE id >= a",
"WHERE id > 1 and id < 900",
"WHERE id = 1",
"WHERE id in (1,3,4,600,900,955,966,977,978)",
"WHERE a in (1,3,4,600,9200,955,966,977,978)",
"WHERE a>2 and a<9000"
};
private final static String[] projectConditions = new String[] {
"id,c,a,b",
"id,c",
"a,b",
"*",
"id,a,b,c",
"id,a",
"a,b,c",
"a,c"
};
private static BaseTestSuite createTestCases(final String modelName) {
BaseTestSuite suite = new BaseTestSuite();
for (int doPos = 0; doPos<2; doPos++) {
boolean positioned = doPos>0; // true if to use positioned updates
for (int i = 0; i < selectConditions.length; i++) {
for (int j = 0; j < projectConditions.length; j++) {
final String cursorName = "cursor_" + i + "_" + j;
final String stmtString = "SELECT " + projectConditions[j] +
" FROM T1 " + selectConditions[i];
suite.addTest(new SURQueryMixTest(modelName, stmtString, cursorName,
positioned));
}
}
}
return suite;
}
/**
* Run in client and embedded.
*/
public static Test suite()
{
BaseTestSuite mainSuite = new BaseTestSuite("SURQueryMixTest suite");
mainSuite.addTest(baseSuite("SURQueryMixTest:embedded"));
mainSuite.addTest(
TestConfiguration.clientServerDecorator(
baseSuite("SURQueryMixTest:client")));
return mainSuite;
}
/**
* The suite contains all testcases in this class running on different data models
*/
private static Test baseSuite(String name) {
BaseTestSuite mainSuite = new BaseTestSuite(name);
// Iterate over all data models and decorate the tests:
for (Iterator i = SURDataModelSetup.SURDataModel.values().iterator();
i.hasNext();) {
SURDataModelSetup.SURDataModel model =
(SURDataModelSetup.SURDataModel) i.next();
BaseTestSuite suite = createTestCases(model.toString());
TestSetup decorator = new SURDataModelSetup(suite, model);
mainSuite.addTest(decorator);
}
return mainSuite;
}
}