/* * (C) Copyright IBM Corp. 2008 * * LICENSE: Eclipse Public License v1.0 * http://www.eclipse.org/legal/epl-v10.html */ package com.ibm.db2j; import java.io.File; import java.io.IOException; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.Collections; import java.util.Map; import java.util.Stack; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.derby.iapi.error.StandardException; import org.apache.derby.iapi.store.access.Qualifier; import org.apache.derby.iapi.types.DataValueDescriptor; import org.apache.derby.vti.IFastPath; import org.apache.derby.vti.VTICosting; import org.apache.derby.vti.VTIEnvironment; import com.ibm.gaiandb.CachedHashMap; import com.ibm.gaiandb.DataSourcesManager; import com.ibm.gaiandb.EntityMatrixJoiner; import com.ibm.gaiandb.GaianChildRSWrapper; import com.ibm.gaiandb.GaianChildVTI; import com.ibm.gaiandb.GaianDBConfig; import com.ibm.gaiandb.GaianResultSetMetaData; import com.ibm.gaiandb.Logger; import com.ibm.gaiandb.diags.GDBMessages; /** * @author DavidVyvyan */ public class EntityAssociations extends VTI60 implements VTICosting, IFastPath, GaianChildVTI { /*Pushable, IQualifyable, */ // Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice. public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2008"; private static final Logger logger = new Logger( "EntityAssociations", 25 ); public static final String PROPERTY_ENTITY_ASSOCIATIONS_INPUT_FILE = "fileName"; public static final String PROPERTY_ENTITY_ASSOCIATIONS_OUTPUT_FILE = "outputFile"; public static final String PROPERTY_ENTITY_ASSOCIATIONS_NUM_ENTITIES = "numEntities"; public static final String PROPERTY_ENTITY_ASSOCIATIONS_GROUP_SIZE = "maxGroupSize"; private static final String GROUPZISE_COLNAME = "GROUPSIZE"; private static final String ENTITY_COLNAME_PREFIX = "ENTITY"; private ResultSetMetaData rsmd; private String inputFile; private String logicalTable; private int restrictedGroupSize; private EntityMatrixJoiner emj = null; private int index = 0; // filename -> latest row count private static Map<String, Long> estimatedRowCounts = Collections.synchronizedMap( new CachedHashMap<String, Long>( GaianTable.QRY_METADATA_CACHE_SIZE ) ); // filename -> matrix computed at initialisation private static ConcurrentMap<String, EntityMatrixJoiner> matrices = new ConcurrentHashMap<String, EntityMatrixJoiner>(); private static ConcurrentMap<String, Long> loadTimes = new ConcurrentHashMap<String, Long>(); public static boolean loadMatrix( String inputFileName, int numEntities, int maxGroupSize, String outputFileName ) { try { EntityMatrixJoiner emj = new EntityMatrixJoiner(inputFileName, numEntities, maxGroupSize); emj.processJoins(); if ( null != outputFileName ) { emj.writeNonOverflowedRowsToFile( outputFileName ); emj.releaseMatrixFromMemory(); } else { matrices.put( new File( inputFileName ).getCanonicalPath().intern(), emj ); loadTimes.put( new File( inputFileName ).getCanonicalPath().intern(), new Long( System.currentTimeMillis() ) ); } } catch ( Exception e ) { logger.logException( GDBMessages.DSWRAPPER_MATRIX_JOINER_LOAD_WRITE_ERROR, "Unable to load Entity Matrix Joiner associations or write them to output file (inputFile: " + inputFileName + ", numEntities: " + numEntities + ", maxGroupSize: " + maxGroupSize + ", outputFile: " + outputFileName + ")", e); return false; } return true; } public static void unloadMatrix( String inputFileName ) throws IOException { if ( null == inputFileName ) return; EntityMatrixJoiner emj = (EntityMatrixJoiner) matrices.remove( new File( inputFileName ).getCanonicalPath().intern() ); loadTimes.remove( new File( inputFileName ).getCanonicalPath().intern() ); if ( null != emj ) emj.releaseMatrixFromMemory(); } public static long getLoadTime( String inputFileName ) { Long l = null; try { l = (Long) loadTimes.get( new File( inputFileName ).getCanonicalPath().intern() ); } catch (IOException e) {} if ( null == l ) return 0; return l.longValue(); } // No logical table involved - straight access to local data only (in-mem) public EntityAssociations() throws Exception { this( new Integer(-1) ); } public EntityAssociations( Integer inputGroupSize ) throws Exception { this( inputGroupSize, null ); } public EntityAssociations( String logicalTable ) throws Exception { this( null, logicalTable ); } public EntityAssociations( String logicalTable, Integer inputGroupSize ) throws Exception { this( inputGroupSize, logicalTable ); } public EntityAssociations( Integer inputGroupSize, String logicalTable ) throws Exception { super(); logger.logInfo("Constructor: new EntityAssociations(" + logicalTable + ", " + inputGroupSize + ")" ); setArgs( logicalTable, null == inputGroupSize ? -1 : inputGroupSize.intValue() ); } private void setArgs( String logicalTable, int inputGroupSize ) throws Exception { this.inputFile = GaianDBConfig.getVTIProperty( EntityAssociations.class, PROPERTY_ENTITY_ASSOCIATIONS_INPUT_FILE ); this.logicalTable = logicalTable; // if ( null == inputGroupSize ) { // EntityMatrixJoiner emj = (EntityMatrixJoiner) matrices.get( new File( outputFile ).getCanonicalPath().intern() ); // if ( null != emj ) restrictedGroupSize = emj.getNumGroups(); // else restrictedGroupSize = -1; // Not defined // } else // restrictedGroupSize = inputGroupSize.intValue(); restrictedGroupSize = inputGroupSize; int numEntityColumns = 1 < restrictedGroupSize ? restrictedGroupSize : Integer.parseInt(GaianDBConfig.getVTIProperty( EntityAssociations.class, PROPERTY_ENTITY_ASSOCIATIONS_GROUP_SIZE )); StringBuffer cols = new StringBuffer( GROUPZISE_COLNAME + " INT" ); for ( int i=1; i<=numEntityColumns; i++ ) cols.append( ", " + ENTITY_COLNAME_PREFIX + i + " INT" ); // try { // // // } catch ( Exception e ) { // logger.logException("Error constructing EntityAssociations()", e); // } rsmd = new GaianResultSetMetaData( cols.toString() ); } /* (non-Javadoc) * @see com.ibm.gaiandb.GaianChildVTI#setArgs(java.lang.String[]) */ public void setArgs(String[] args) throws Exception { // if ( 1 < args.length ) // setArgs( args[0], Integer.parseInt( args[1] ) ); if ( 0 < args.length ) { String[] elmts = args[0].split(" "); if ( 1 < elmts.length ) setArgs( elmts[0], Integer.parseInt( elmts[1] ) ); else if ( 0 < elmts.length ) setArgs( elmts[0], -1 ); } else setArgs( null, -1 ); } /* (non-Javadoc) * @see com.ibm.gaiandb.GaianChildVTI#setExtractConditions(org.apache.derby.iapi.store.access.Qualifier[][], int[]) */ public void setExtractConditions(Qualifier[][] qualifiers, int[] logicalProjection, int[] columnsMapping) { // No need to set qualifiers - the search string acts as a filter instead. // Also ignore mappedColumns as column names are expected to be the same in the logical table. } /* (non-Javadoc) * @see org.apache.derby.vti.IFastPath#executeAsFastPath() */ public boolean executeAsFastPath() throws StandardException, SQLException { logger.logInfo("EntityAssociations.executeAsFastPath()"); try { emj = (EntityMatrixJoiner) matrices.get( new File( inputFile ).getCanonicalPath().intern() ); } catch (IOException e1) { logger.logWarning(GDBMessages.DSWRAPPER_ENTITYMATRIXJOINER_GET_WARNING, "Unable to get EntityMatrixJoiner from fileName, cause: " + e1); } if ( null == emj ) { if ( null == logicalTable ) { String msg = "NO LOADED ENTITY ASSOCIATIONS FOR: " + inputFile; logger.logWarning(GDBMessages.DSWRAPPER_NO_ENTITIY_ASSOC, msg ); return true; } logger.logInfo("No in-mem associations for local file " + inputFile + " - they will be loaded as part of distributed qry"); } try { // If this is a Logical Table query, we want to query more than just the local file. // We want the local matrix plus all matrices from other connected nodes. if ( null != logicalTable ) { String query = "select * from new com.ibm.db2j.GaianTable('" + logicalTable + ( 1<restrictedGroupSize ? "', 'EntityAssociationsVTIARGS=" + restrictedGroupSize + "'" : "" ) + "') GT"; // "', '', '', '" + ( null == emj ? "" : GaianDBConfig.getGaianNodeID() ) + "') GT"; // Run a distributed query to get the rows from other nodes - pass in our table definition so that // the local node can just act as a gateway to the others (as it needs the meta-data for this). // The database driver for the local derby is already loaded String cdetails = GaianDBConfig.getLocalDerbyConnectionID(); Stack<Object> connectionPool = DataSourcesManager.getSourceHandlesPool( cdetails ); GaianChildVTI nodesRows = new GaianChildRSWrapper( // DriverManager.getConnection( "jdbc:derby://localhost:" + // GaianDBConfig.getDerbyServerListenerPort() + "/" + GaianDBConfig.getGaianNodeDatabasePath(), // GaianDBConfig.getGaianNodeUser(), GaianDBConfig.getGaianNodePassword()) DataSourcesManager.getPooledJDBCConnection( cdetails, connectionPool ) .createStatement().executeQuery( query ) ); // emj.clone(); if ( null == emj ) { int numEntities = Integer.parseInt(GaianDBConfig.getVTIProperty( EntityAssociations.class, PROPERTY_ENTITY_ASSOCIATIONS_NUM_ENTITIES )); int maxGroupSize = Integer.parseInt(GaianDBConfig.getVTIProperty( EntityAssociations.class, PROPERTY_ENTITY_ASSOCIATIONS_GROUP_SIZE )); emj = new EntityMatrixJoiner( nodesRows, numEntities, maxGroupSize ); } else emj.mergeGaianChildRows( nodesRows ); emj.processJoins(); } } catch ( Exception e) { logger.logException(GDBMessages.DSWRAPPER_DIST_QUERY_PROCESS_ERROR, "Unable to process distributed query and/or its results", e); } // restrictedGroupSize must be >= 2 - Ignore it if it isn't if ( 1 < restrictedGroupSize ) try { emj.setGroupSizeRestriction( restrictedGroupSize ); } catch ( Exception e ) { logger.logException(GDBMessages.DSWRAPPER_RESTRICTED_GPSIZE_SET_ERROR, "Unexpected Exception whilst setting restricted group size", e); }; index = 0; return true; } /* (non-Javadoc) * @see org.apache.derby.vti.IFastPath#nextRow(org.apache.derby.iapi.types.DataValueDescriptor[]) */ public int nextRow(DataValueDescriptor[] dvdr) throws StandardException, SQLException { if ( null == emj ) return IFastPath.SCAN_COMPLETED; // emj wasn't set because no corresponding rows were loaded try { if ( index >= emj.getNumGroups() ) { // Keep track of row count for this search string Long previousCount = estimatedRowCounts.get( logicalTable ); if ( null == previousCount || index > previousCount.longValue() ) estimatedRowCounts.put( logicalTable, new Long( index ) ); if ( !matrices.containsKey( new File( inputFile ).getCanonicalPath().intern() ) ) { // Clear mem ASAP before a new load (dont reset index) - this means this VTI does not support multiple instantiations emj.releaseMatrixFromMemory(); emj = null; } else index = 0; return IFastPath.SCAN_COMPLETED; } int groupHead = emj.getGroupHead( index++ ); int[] row = emj.getGroupRow( groupHead ); dvdr[0].setValue( row[0]+1 ); // the count dvdr[1].setValue( groupHead ); // the head entity for ( int i=2; i<dvdr.length; i++ ) // ...and all its connected entities dvdr[i].setValue( row[i-1] ); } catch ( Exception e ) { logger.logException(GDBMessages.DSWRAPPER_NEXT_ROW_GET_ERROR, "Unexpected Exception whilst getting next row", e); return IFastPath.SCAN_COMPLETED; }; return IFastPath.GOT_ROW; } /* (non-Javadoc) * @see org.apache.derby.vti.IFastPath#currentRow(java.sql.ResultSet, org.apache.derby.iapi.types.DataValueDescriptor[]) */ public void currentRow(ResultSet arg0, DataValueDescriptor[] arg1) throws StandardException, SQLException { } /* (non-Javadoc) * @see org.apache.derby.vti.IFastPath#rowsDone() */ public void rowsDone() throws StandardException, SQLException { close(); } /** * Provide the metadata for the query against the given table. Cloudscape * calls this method when it compiles the SQL-J statement that uses this * VTI class. * * @return the result set metadata for the query * * @exception SQLException thrown by JDBC calls */ public ResultSetMetaData getMetaData() throws SQLException { logger.logInfo("EntityAssociations.getMetaData()"); return rsmd; } /** * Explicitly closes this PreparedStatement class. (Note: Cloudscape * calls this method only after compiling a SELECT statement that uses * this class.)<p> * * @see java.sql.Statement * * @exception SQLException on unexpected JDBC error */ public void close() { logger.logInfo("EntityAssociations.close()"); reinitialise(); } @Override public boolean reinitialise() { logger.logInfo("EntityAssociations.reinitialise()"); index = 0; return true; } public boolean isBeforeFirst() { return 0 == index; } /* (non-Javadoc) * @see org.apache.derby.vti.VTICosting#getEstimatedRowCount(org.apache.derby.vti.VTIEnvironment) */ public double getEstimatedRowCount(VTIEnvironment arg0) throws SQLException { Long l = estimatedRowCounts.get( logicalTable ); double val = null == l ? 100 : l.doubleValue(); logger.logInfo("getEstimatedRowCount() returning: " + val); return val; } /* (non-Javadoc) * @see org.apache.derby.vti.VTICosting#getEstimatedCostPerInstantiation(org.apache.derby.vti.VTIEnvironment) */ public double getEstimatedCostPerInstantiation(VTIEnvironment arg0) throws SQLException { int rc = 0; logger.logInfo("getEstimatedCostPerInstantiation() returning: " + rc); return rc; } /* (non-Javadoc) * @see org.apache.derby.vti.VTICosting#supportsMultipleInstantiations(org.apache.derby.vti.VTIEnvironment) */ public boolean supportsMultipleInstantiations(VTIEnvironment arg0) throws SQLException { // For us to return true, we must ensure that results can be fetched repeatedly. // i.e. that the cursor is set back to the first row every time we reach the end of it. boolean rc = true; logger.logInfo("supportsMultipleInstantiations() returning: " + rc); return rc; } public boolean pushProjection(VTIEnvironment arg0, int[] arg1) throws SQLException { return false; } public void setQualifiers(VTIEnvironment arg0, Qualifier[][] arg1) throws SQLException { } public boolean fetchNextRow(DataValueDescriptor[] row) throws Exception { return false; } public int getRowCount() throws Exception { return emj.getNumGroups(); } public boolean isScrollable() { return false; } }