Skip to content
Snippets Groups Projects
Commit 8915359b authored by gmantele's avatar gmantele
Browse files

[TAP] Add the interface TableIterator (whose implementations let iterate over...

[TAP] Add the interface TableIterator (whose implementations let iterate over any kind of table data set) and its first implementation for ResultSet + JUnit test case
parent 3ee8d15f
No related branches found
No related tags found
No related merge requests found
package tap.data;
import tap.TAPException;
/**
* Exception that occurs when reading a data input (can be an InputStream, a ResultSet, a SavotTable, ...).
*
* @author Grégory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de
* @version 2.0 (06/2014)
* @since 2.0
*
* @see TableIterator
*/
public class DataReadException extends TAPException {
private static final long serialVersionUID = 1L;
public DataReadException(final String message){
super(message);
}
public DataReadException(Throwable cause){
super(cause);
}
public DataReadException(String message, Throwable cause){
super(message, cause);
}
}
package tap.data;
/*
* This file is part of TAPLibrary.
*
* TAPLibrary 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 3 of the License, or
* (at your option) any later version.
*
* TAPLibrary 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 TAPLibrary. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2014 - Astronomisches Rechen Institut (ARI)
*/
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.NoSuchElementException;
/**
* <p>{@link TableIterator} which lets iterate over a SQL {@link ResultSet}.</p>
*
* <p>{@link #getColType()} will return the type declared in the {@link ResultSetMetaData} object.</p>
*
* @author Gr&eacute;gory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de
* @version 2.0 (06/2014)
* @since 2.0
*/
public class ResultSetTableIterator implements TableIterator {
/** ResultSet/Dataset to read. */
private final ResultSet data;
/** Number of columns to read. */
private final int nbColumns;
/** Type of all columns. */
private final String[] colTypes;
/** Indicate whether the row iteration has already started. */
private boolean iterationStarted = false;
/** Indicate whether the last row has already been reached. */
private boolean endReached = false;
/** Index of the last read column (=0 just after {@link #nextRow()} and before {@link #nextCol()}, ={@link #nbColumns} after the last column has been read). */
private int colIndex;
/**
* Build a TableIterator able to read rows and columns of the given ResultSet.
*
* @param dataSet Dataset over which this iterator must iterate.
*
* @throws NullPointerException If NULL is given in parameter.
* @throws DataReadException If the given ResultSet is closed
* or if the metadata (columns count and types) can not be fetched.
*/
public ResultSetTableIterator(final ResultSet dataSet) throws NullPointerException, DataReadException{
// A dataset MUST BE provided:
if (dataSet == null)
throw new NullPointerException("Missing ResultSet object over which to iterate!");
// It MUST also BE OPEN:
try{
if (dataSet.isClosed())
throw new DataReadException("Closed ResultSet: impossible to iterate over it!");
}catch(SQLException se){
throw new DataReadException("Can not know whether the ResultSet is open!", se);
}
// Keep a reference to the ResultSet:
data = dataSet;
// Count columns and determine their type:
try{
// get the metadata:
ResultSetMetaData metadata = data.getMetaData();
// count columns:
nbColumns = metadata.getColumnCount();
// determine their type:
colTypes = new String[nbColumns];
for(int i = 1; i <= nbColumns; i++)
colTypes[i - 1] = metadata.getColumnTypeName(i);
}catch(SQLException se){
throw new DataReadException("Can not get the column types of the given ResultSet!", se);
}
}
@Override
public boolean nextRow() throws DataReadException{
try{
// go to the next row:
boolean rowFetched = data.next();
endReached = !rowFetched;
// prepare the iteration over its columns:
colIndex = 0;
iterationStarted = true;
return rowFetched;
}catch(SQLException e){
throw new DataReadException("Unable to read a result set row!", e);
}
}
/**
* <p>Check the row iteration state. That's to say whether:</p>
* <ul>
* <li>the row iteration has started = the first row has been read = a first call of {@link #nextRow()} has been done</li>
* <li>AND the row iteration is not finished = the last row has been read.</li>
* </ul>
* @throws IllegalStateException
*/
private void checkReadState() throws IllegalStateException{
if (!iterationStarted)
throw new IllegalStateException("No row has yet been read!");
else if (endReached)
throw new IllegalStateException("End of ResultSet already reached!");
}
@Override
public boolean hasNextCol() throws IllegalStateException, DataReadException{
// Check the read state:
checkReadState();
// Determine whether the last column has been reached or not:
return (colIndex < nbColumns);
}
@Override
public Object nextCol() throws NoSuchElementException, IllegalStateException, DataReadException{
// Check the read state and ensure there is still at least one column to read:
if (!hasNextCol())
throw new NoSuchElementException("No more column to read!");
// Get the column value:
try{
return data.getObject(++colIndex);
}catch(SQLException se){
throw new DataReadException("Can not read the value of the " + colIndex + "-th column!", se);
}
}
@Override
public String getColType() throws IllegalStateException, DataReadException{
// Basically check the read state (for rows iteration):
checkReadState();
// Check deeper the read state (for columns iteration):
if (colIndex <= 0)
throw new IllegalStateException("No column has yet been read!");
else if (colIndex > nbColumns)
throw new IllegalStateException("All columns have already been read!");
// Return the column type:
return colTypes[colIndex - 1];
}
}
package tap.data;
/*
* This file is part of TAPLibrary.
*
* TAPLibrary 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 3 of the License, or
* (at your option) any later version.
*
* TAPLibrary 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 TAPLibrary. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2014 - Astronomisches Rechen Institut (ARI)
*/
import java.util.NoSuchElementException;
/**
* <p>Let's iterate on each row and then on each column over a table dataset.</p>
*
* <p>Initially, no rows are loaded and the "cursor" inside the dataset is set before the first row.
* Thus, a first call to {@link #nextRow()} is required to read each of the column values of the first row.</p>
*
* <p>Example of an expected usage:</p>
* <pre>
* TableIterator it = ...;
* try{
* while(it.nextRow()){
* while(it.hasNextCol()){
* Object colValue = it.nextCol();
* String colType = it.getColType();
* ...
* }
* }
* }catch(DataReadException dre){
* ...
* }
* </pre>
*
* @author Gr&eacute;gory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de
* @version 2.0 (06/2014)
* @since 2.0
*/
public interface TableIterator {
/**
* <p>Go to the next row if there is one.</p>
*
* <p><i>Note: After a call to this function the columns must be fetched individually using {@link #nextCol()}
* <b>IF</b> this function returned </i>true<i>.</i></p>
*
* @return <i>true</i> if the next row has been successfully reached,
* <i>false</i> if no more rows can be read.
*
* @throws DataReadException If an error occurs while reading the table dataset.
*/
public boolean nextRow() throws DataReadException;
/**
* Tell whether another column is available.
*
* @return <i>true</i> if {@link #nextCol()} will return the value of the next column with no error,
* <i>false</i> otherwise.
*
* @throws IllegalStateException If {@link #nextRow()} has not yet been called.
* @throws DataReadException If an error occurs while reading the table dataset.
*/
public boolean hasNextCol() throws IllegalStateException, DataReadException;
/**
* <p>Return the value of the next column.</p>
*
* <p><i>Note: The column type can be fetched using {@link #getColType()} <b>after</b> a call to {@link #nextCol()}.</i></p>
*
* @return Get the value of the next column.
*
* @throws NoSuchElementException If no more column value is available.
* @throws IllegalStateException If {@link #nextRow()} has not yet been called.
* @throws DataReadException If an error occurs while reading the table dataset.
*/
public Object nextCol() throws NoSuchElementException, IllegalStateException, DataReadException;
/**
* <p>Get the type of the current column value.</p>
*
* <p><i>Note: "Current column value" means here "the value last returned by {@link #nextCol()}".</i></p>
*
* @return Type of the current column value,
* or NULL if this information is not available or if this function is not implemented.
*
* @throws IllegalStateException If {@link #nextCol()} has not yet been called.
* @throws DataReadException If an error occurs while reading the table dataset.
*/
public String getColType() throws IllegalStateException, DataReadException;
}
package tap.data;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.sql.Connection;
import java.sql.ResultSet;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import testtools.DBTools;
public class ResultSetTableIteratorTest {
private static Connection conn;
@BeforeClass
public static void setUpBeforeClass() throws Exception{
conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd");
}
@AfterClass
public static void tearDownAfterClass() throws Exception{
DBTools.closeConnection(conn);
}
@Test
public void testWithRSNULL(){
try{
new ResultSetTableIterator(null);
fail("The constructor should have failed, because: the given ResultSet is NULL.");
}catch(Exception ex){
assertEquals(ex.getClass().getName(), "java.lang.NullPointerException");
}
}
@Test
public void testWithData(){
try{
ResultSet rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;");
TableIterator it = new ResultSetTableIterator(rs);
final int expectedNbLines = 10, expectedNbColumns = 4;
int countLines = 0, countColumns = 0;
while(it.nextRow()){
// count lines:
countLines++;
// reset columns count:
countColumns = 0;
while(it.hasNextCol()){
it.nextCol();
// count columns
countColumns++;
// TEST the column type is set (not null):
assertTrue(it.getColType() != null);
}
// TEST that all columns have been read:
assertEquals(expectedNbColumns, countColumns);
}
// TEST that all lines have been read:
assertEquals(expectedNbLines, countLines);
}catch(Exception ex){
ex.printStackTrace(System.err);
fail("An exception occurs while reading a correct ResultSet (containing some valid rows).");
}
}
@Test
public void testWithEmptySet(){
try{
ResultSet rs = DBTools.select(conn, "SELECT * FROM gums WHERE id = 'foo';");
TableIterator it = new ResultSetTableIterator(rs);
int countLines = 0;
// count lines:
while(it.nextRow())
countLines++;
// TEST that no line has been read:
assertEquals(countLines, 0);
}catch(Exception ex){
ex.printStackTrace(System.err);
fail("An exception occurs while reading a correct ResultSet (containing some valid rows).");
}
}
@Test
public void testWithClosedSet(){
try{
// create a valid ResultSet:
ResultSet rs = DBTools.select(conn, "SELECT * FROM gums WHERE id = 'foo';");
// close the ResultSet:
rs.close();
// TRY to create a TableIterator with a closed ResultSet:
new ResultSetTableIterator(rs);
fail("The constructor should have failed, because: the given ResultSet is closed.");
}catch(Exception ex){
assertEquals(ex.getClass().getName(), "tap.data.DataReadException");
}
}
}
package testtools;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
public final class DBTools {
public static int count = 0;
public final static void main(final String[] args) throws Throwable{
for(int i = 0; i < 3; i++){
Thread t = new Thread(new Runnable(){
@Override
public void run(){
count++;
try{
Connection conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd");
System.out.println("Start - " + count + ": ");
String query = "SELECT * FROM gums.smc WHERE magg BETWEEN " + (15 + count) + " AND " + (20 + count) + ";";
System.out.println(query);
ResultSet rs = DBTools.select(conn, query);
try{
rs.last();
System.out.println("Nb rows: " + rs.getRow());
}catch(SQLException e){
e.printStackTrace();
}
if (DBTools.closeConnection(conn))
System.out.println("[DEBUG] Connection closed!");
}catch(DBToolsException e){
e.printStackTrace();
}
System.out.println("End - " + count);
count--;
}
});
t.start();
}
}
public static class DBToolsException extends Exception {
private static final long serialVersionUID = 1L;
public DBToolsException(){
super();
}
public DBToolsException(String message, Throwable cause){
super(message, cause);
}
public DBToolsException(String message){
super(message);
}
public DBToolsException(Throwable cause){
super(cause);
}
}
public final static HashMap<String,String> VALUE_JDBC_DRIVERS = new HashMap<String,String>(4);
static{
VALUE_JDBC_DRIVERS.put("oracle", "oracle.jdbc.OracleDriver");
VALUE_JDBC_DRIVERS.put("postgresql", "org.postgresql.Driver");
VALUE_JDBC_DRIVERS.put("mysql", "com.mysql.jdbc.Driver");
VALUE_JDBC_DRIVERS.put("sqlite", "org.sqlite.JDBC");
}
private DBTools(){}
public final static Connection createConnection(String dbms, final String server, final String port, final String dbName, final String user, final String passwd) throws DBToolsException{
// 1. Resolve the DBMS and get its JDBC driver:
if (dbms == null)
throw new DBToolsException("Missing DBMS (expected: oracle, postgresql, mysql or sqlite)!");
dbms = dbms.toLowerCase();
String jdbcDriver = VALUE_JDBC_DRIVERS.get(dbms);
if (jdbcDriver == null)
throw new DBToolsException("Unknown DBMS (\"" + dbms + "\")!");
// 2. Load the JDBC driver:
try{
Class.forName(jdbcDriver);
}catch(ClassNotFoundException e){
throw new DBToolsException("Impossible to load the JDBC driver: " + e.getMessage(), e);
}
// TODO DEBUG MSG
System.out.println("[DEBUG] " + dbms + " JDBC Driver Registered!");
// 3. Establish the connection:
Connection connection = null;
try{
connection = DriverManager.getConnection("jdbc:" + dbms + "://" + server + ((port != null && port.trim().length() > 0) ? (":" + port) : "") + "/" + dbName, user, passwd);
}catch(SQLException e){
throw new DBToolsException("Connection failed: " + e.getMessage(), e);
}
if (connection == null)
throw new DBToolsException("Failed to make connection!");
// TODO DEBUG MSG
System.out.println("[DEBUG] Connection to " + dbName + " established!");
return connection;
}
public final static boolean closeConnection(final Connection conn) throws DBToolsException{
try{
if (conn != null && !conn.isClosed()){
conn.close();
try{
Thread.sleep(200);
}catch(InterruptedException e){
System.err.println("WARNING: can't wait/sleep before testing the connection close status! [" + e.getMessage() + "]");
}
return conn.isClosed();
}else
return true;
}catch(SQLException e){
throw new DBToolsException("Closing connection failed: " + e.getMessage(), e);
}
}
public final static ResultSet select(final Connection conn, final String selectQuery) throws DBToolsException{
if (conn == null || selectQuery == null || selectQuery.trim().length() == 0)
throw new DBToolsException("One parameter is missing!");
try{
Statement stmt = conn.createStatement();
return stmt.executeQuery(selectQuery);
}catch(SQLException e){
throw new DBToolsException("Can't execute the given SQL query: " + e.getMessage(), e);
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment