/* * (C) Copyright IBM Corp. 2010 * * LICENSE: Eclipse Public License v1.0 * http://www.eclipse.org/legal/epl-v10.html */ package com.ibm.gaiandb.udpdriver.server; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Types; import java.util.ArrayList; import org.apache.derby.iapi.types.DataValueDescriptor; import org.apache.derby.vti.IFastPath; import com.ibm.gaiandb.udpdriver.common.RowsFilter; import com.ibm.gaiandb.udpdriver.common.protocol.Message; import com.ibm.gaiandb.udpdriver.common.protocol.ResponseWithValues; /** * ValuesEncoder transforms database records into a binary format allowing them to be sent * through the network. * It fetches the records from a java.sql.ResultSet, obtained from the Derby Embedded driver. * * A ValuesEncoder instance is linked to a query because of the meta-data. So it could be re-used * in case of re-execution of the query re-executed (PreapredStatement)by setting the new * ResultSet object with the reUseWithNewResultSet() method. * * @author lengelle * */ public class ValuesEncoder { // Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice. public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2010"; private PreparedStatement preparedStatement; private ResultSet resultSet; private int maxLength; private int numberOfColumns; private int[] columnTypes; private DataValueDescriptor[] currentRow; private DataValueDescriptor[] recycleRow; private boolean[] nullableColumns; private boolean[] columnsWithChar; /** * Instantiates a new ValuesEncoder. * * @param resultSet * @param maxLength the maximum size expected for the records serialized form * @param columnTypes * @param nullableColumnsList * @throws UDPDriverServerException */ public ValuesEncoder( /*ResultSet resultSet,*/ PreparedStatement preparedStatement, int maxLength, int[] columnTypes, ArrayList<Integer> nullableColumnsList ) throws UDPDriverServerException { try { // if ( resultSet==null ) // { // throw new UDPDriverServerException( "ResultSet is null." ); // } // this.resultSet = resultSet; this.preparedStatement = preparedStatement; reExecutePreparedStatementQuery(); this.maxLength = maxLength; numberOfColumns = columnTypes.length; this.columnTypes = columnTypes; currentRow = null; // Set a boolean array indicating nullable columns nullableColumns = new boolean[ numberOfColumns ]; int nullableColumnsListSize = nullableColumnsList.size(); int nullableColumnsListIndex = 0; for ( int i=0; i<nullableColumns.length; ++i ) { if ( nullableColumnsList != null && nullableColumnsListSize != 0 && nullableColumnsListIndex<nullableColumnsListSize && nullableColumnsList.get( nullableColumnsListIndex ).equals( i+1 ) ) { nullableColumns[i] = true; ++nullableColumnsListIndex; } else { nullableColumns[i] = false; } } // Set a boolean array indicating CHAR, VARCHAR, LONGVARCHAR columns columnsWithChar = new boolean[ numberOfColumns ]; for ( int i=0; i<columnsWithChar.length; ++i ) { if ( columnTypes[i]==Types.CHAR || columnTypes[i]==Types.VARCHAR || columnTypes[i]==Types.LONGVARCHAR ) { columnsWithChar[i] = true; } else { columnsWithChar[i] = false; } } } catch( Exception e ) { throw new UDPDriverServerException( "ValuesEncoder constructor failed.", e ); } } /** * Re-use the ValuesEncoder for a re-execution of the query. * * @param resultSet the new ResultSet * @throws UDPDriverServerException */ // public void reUseWithNewResultSet( ResultSet resultSet ) throws UDPDriverServerException // { // try // { // this.resultSet = resultSet; // currentRow = null; // } // catch( Exception e ) // { // throw new UDPDriverServerException( "ValuesEncoder clean() failed.", e ); // } // } public void reExecutePreparedStatementQuery() throws UDPDriverServerException { try { if ( preparedStatement instanceof IFastPath ) { ((IFastPath)preparedStatement).executeAsFastPath(); } else { resultSet = preparedStatement.executeQuery(); if ( resultSet==null ) { throw new UDPDriverServerException( "ResultSet is null." ); } } currentRow = null; } catch( Exception e ) { throw new UDPDriverServerException( "ValuesEncoder clean() failed.", e ); } } /** * Encode the next database records into the ResponseWithValues given as a parameter. * * @param response * @throws UDPDriverServerException */ public void encodeNextValues( ResponseWithValues response ) throws UDPDriverServerException { try { int responseSize; boolean containsLastValuesCopy; int numberOfRowsCopy; int numberOfNullValuesCopy; int baosSizeCopy; SpecificByteArrayOutputStream baos = new SpecificByteArrayOutputStream( maxLength ); ObjectOutput oos = new ObjectOutputStream( baos ); do { // Fetch a row in the ResultSet if ( currentRow==null ) { currentRow = fetchOneRowInResult( recycleRow ); if ( currentRow==null ) // No more row in the ResultSet { response.setContainsLastValues( true ); oos.flush(); response.setSerializedDVDs( baos.toByteArray() ); oos.close(); baos.close(); return; } } // Copy the current values containsLastValuesCopy = response.containsLastValues(); numberOfRowsCopy = response.getNumberOfRows(); numberOfNullValuesCopy = response.getNumberOfNullValues(); baosSizeCopy = baos.size(); // Add new values to the response addRowToResponse( response, oos, currentRow ); // Calculate the new size oos.flush(); responseSize = response.estimateHeaderSize() + baos.size(); recycleRow = currentRow; currentRow = null; } while( responseSize <= maxLength ); // Detection of the case where one serialized row is longer than maxLength for MetaData if ( response.getNumberOfRows()==1 && response.getType()!=Message.META_DATA ) { response.setSerializedDVDs( baos.toByteArray() ); return; } currentRow = recycleRow; recycleRow = null; // Put the copies in the response response.setContainsLastValues( containsLastValuesCopy ); response.setNumberOfRows( numberOfRowsCopy ); // Remove the latest null values from the list if ( response.getNumberOfNullValues() > 0 ) { for ( int i=0; i<( response.getNumberOfNullValues() - numberOfNullValuesCopy ); ++i ) { response.removeLastNullValue(); } } response.setNumberOfNullValues( numberOfNullValuesCopy ); if ( baosSizeCopy > 4 ) { response.setSerializedDVDs( baos.toByteArray( baosSizeCopy ) ); } oos.close(); baos.close(); return; } catch( Exception e ) { throw new UDPDriverServerException( "ValuesEncoder encodeNextValues() failed.", e ); } } private void addRowToResponse( ResponseWithValues response, ObjectOutput oos, DataValueDescriptor[] dvdr ) throws UDPDriverServerException { try { DataValueDescriptor dvdTemp = null; int currentNumberOfRows = response.getNumberOfRows(); int currentNumberOfNullValues = response.getNumberOfNullValues(); int coefficient = currentNumberOfRows * dvdr.length; for( int i=0; i<dvdr.length; ++i ) { dvdTemp = dvdr[i]; if ( dvdTemp.isNull() ) { ++currentNumberOfNullValues; response.addNullValue( i + coefficient ); } else { // Due to derby serialization issue with empty string "" if ( columnsWithChar[ i ] && dvdTemp.getLength()==1 ) { // Double if because getString() is an expensive operation if ( dvdTemp.getString().equals( "" ) ) { dvdTemp.setValue( "\0" ); } } dvdTemp.writeExternal( oos ); } } response.setContainsLastValues( false ); response.setNumberOfRows( ++currentNumberOfRows ); response.setNumberOfNullValues( currentNumberOfNullValues ); } catch( Exception e ) { throw new UDPDriverServerException( "ValuesEncoder addRowToResponse() failed.", e ); } } private DataValueDescriptor[] fetchOneRowInResult( DataValueDescriptor[] rowToPopulate ) throws UDPDriverServerException { try { if ( preparedStatement instanceof IFastPath ) { if ( null == rowToPopulate ) rowToPopulate = createNewRowTemplate(); IFastPath fp = (IFastPath) preparedStatement; if ( IFastPath.SCAN_COMPLETED == fp.nextRow(rowToPopulate) ) return null; // any newly created rowToPopulate will be dereferenced and garbage collected } else { if ( !resultSet.next() ) return null; if ( null == rowToPopulate ) rowToPopulate = createNewRowTemplate(); for ( int i=0; i<numberOfColumns; i++ ) rowToPopulate[i].setValueFromResultSet( resultSet, i+1, nullableColumns[i] ); } return rowToPopulate; } catch( Exception e ) { throw new UDPDriverServerException( "ValuesEncoder fetchOneRowInResultSet(DVD[]) failed.", e ); } } private DataValueDescriptor[] createNewRowTemplate() { // initialise row DataValueDescriptor[] dvdr = new DataValueDescriptor[ numberOfColumns ]; for ( int i=0; i<numberOfColumns; i++ ) dvdr[i] = RowsFilter.constructDVDMatchingJDBCType( columnTypes[i] ); return dvdr; } }