/* * (C) Copyright IBM Corp. 2008 * * LICENSE: Eclipse Public License v1.0 * http://www.eclipse.org/legal/epl-v10.html */ package com.ibm.gaiandb; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import java.util.Date; import java.util.Map; import java.util.Stack; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; import org.apache.derby.iapi.store.access.Qualifier; import org.apache.derby.iapi.types.DataValueDescriptor; import org.apache.derby.vti.IFastPath; import com.ibm.db2j.FileImport; import com.ibm.db2j.GaianTable; import com.ibm.gaiandb.DataSourcesManager.RDBProvider; import com.ibm.gaiandb.diags.GDBMessages; /** * A VTIWrapper for JDBC data sources. * * @author DavidVyvyan */ public class VTIRDBResult extends VTIWrapper { // 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( "VTIRDBResult", 30 ); // private static final int PREPARED_STMTS_CACHE_SIZE = 100; // private final Map cachedPreparedStatements = Collections.synchronizedMap( new PreparedStatementsCache(PREPARED_STMTS_CACHE_SIZE) ); // private final Map reversePreparedStatementsSQLLookup = new Hashtable(); private static final int PREPARED_STMTS_CACHE_SIZE_PER_CONNECTION = 100; // private static final int PREPARED_STMTS_CACHE_SIZE = 100; // Connection -> <SQL,PreparedStatement> // Structure used to find already prepared statements on a connection taken from the pool. private static final ConcurrentMap<Connection, Map<String, PreparedStatement>> preparedStatementsOfConnections = new ConcurrentHashMap<Connection, Map<String, PreparedStatement>>(); public static final void clearPreparedStatementsCacheForConnection( Connection c ) throws SQLException { Map<String, PreparedStatement> pstmts = preparedStatementsOfConnections.remove(c); if ( null != pstmts ) { for ( Statement s : pstmts.values() ) s.close(); pstmts.clear(); } } // Time at which to re-enable the vti data source if it was disabled on account of the connection being lost private long reenablementTime = 0; private static final int DISABLEMENT_PERIOD_MS = 5000; // TODO: This value should maybe be linked to the connection checker heartbeat ? // // PreparedStatement -> SQL // private final Map preparedStatementsSQL = // Collections.synchronizedMap( new PreparedStatementsCache(PREPARED_STMTS_CACHE_SIZE) ); // // // SQL -> Stack of PreparedStatement objects // private final ConcurrentMap<String,Stack> cachedPreparedStatements = new ConcurrentHashMap(); // // private final class PreparedStatementsCache extends CachedHashMap { // private PreparedStatementsCache( int cacheSize ) { // super( cacheSize ); // } // // protected boolean removeEldestEntry(Map.Entry eldest) { // // if ( size() <= cacheSize ) return false; // // PreparedStatement pstmt = (PreparedStatement) eldest.getKey(); //// PreparedStatement pstmt = (PreparedStatement) eldest.getValue(); // // String sql = (String) eldest.getValue(); // // Stack pstmts = (Stack) cachedPreparedStatements.get(sql); // if ( null != pstmts ) { // if ( pstmts.remove(pstmt) ) { // try { pstmt.close(); } // Only do this close() here as we know the pstmt is not currently in use. // catch (SQLException e) {} // Ignore exception - i.e. already closed.. // } // if ( pstmts.isEmpty() ) { // logger.logInfo("REMOVING CACHED PREPARED STMTS ENTRY"); // cachedPreparedStatements.remove(sql); // } // } // return true; // } // } // private static final String[] TRIGEVENTS = { "DELETE", "UPADTE", "INSERT" }; // GaianChildVTI (GaianChildRSWrapper) -> Long exec time private static final ConcurrentMap<GaianChildVTI, Long> rdbExecTimes = new ConcurrentHashMap<GaianChildVTI, Long>(); private final String connectionDetails; private final RDBProvider rdbmsProvider; private final boolean isLocalDerbyDataSource; private String physicalTable = null; // Shorthand table expressions (i.e. not wrapped in sub-query), example: LTN_TABLE=PTABLE1,PTABLE2 WHERE PTABLE1.A=TABLE2.B // This cannot be used if there are column name clashes, e.g. if PTABLE1 and PTABLE2 have some matching column names. private int shorthandTableXprWhereClauseIndex = -1; private boolean isGaianNode = false; private boolean isSubQuery = false; // private boolean isTableNeedsRefresh = false; // Not currently used - would be used to trigger a refresh for in memory tables... // List used to track down the connection attached to a hanging statement. // We need a synchronized List due to concurrent threads - hence Vector rather than ArrayList // A Stack allows faster removal time than Vector for the fast queries because it searches from the first element in the list. private Stack<Connection> activeConnections = new Stack<Connection>(); public VTIRDBResult( String connectionDetails, String nodeDefName, GaianResultSetMetaData logicalTableRSMD ) throws Exception { super( connectionDetails, nodeDefName ); this.connectionDetails = connectionDetails; this.rdbmsProvider = RDBProvider.fromGaianConnectionID(connectionDetails); logger.logInfo( nodeDefName + " Building new VTIRDBResult based on: " + connectionDetails.substring( 0, connectionDetails.lastIndexOf("'") ) + ", RDBMS provider is " + rdbmsProvider ); // omit password from logs String[] props = GaianDBConfig.getConnectionTokens( connectionDetails ); this.isLocalDerbyDataSource = props[1].startsWith( props[1].startsWith("jdbc:derby://") ? "jdbc:derby://localhost:" + GaianDBConfig.getDerbyServerListenerPort() : "jdbc:derby:"); reinitialise( logicalTableRSMD ); } public boolean isGaianNode() { return isGaianNode; } public boolean isSubQuery() { return isSubQuery; } @Override public String[] getPluralizedInstances() { return null; } @Override public DataValueDescriptor[] getPluralizedInstanceConstants(String dsInstanceID) { return null; } // public ResultSet execute( Qualifier[][] qualifiers, int[] projectedColumns ) throws Exception { // return execute( null, qualifiers, projectedColumns ); // } // private static final ConcurrentHashMap<String, Object> noArguments = new ConcurrentHashMap<String, Object>(0); // private static final int resultPrefetchSize = 20; // // public GaianChildVTI execute( Qualifier[][] qualifiers, int[] projectedColumns, String table ) throws Exception { // return execute( noArguments, qualifiers, projectedColumns, table ); // } public GaianChildVTI execute( ConcurrentMap<String,Object> arguments, Qualifier[][] qualifiers, int[] projectedColumns ) throws Exception { return execute( arguments, qualifiers, projectedColumns, physicalTable ); } protected GaianChildVTI execute( ConcurrentMap<String, Object> arguments, Qualifier[][] qualifiers, int[] projectedColumns, String tableExpression ) throws Exception { GaianChildVTI result = null; if ( null == tableExpression ) tableExpression = physicalTable; // Take a local snapshot of this value in case it changes mid-execution. boolean isInMemRows = isRowsInMemory; logger.logThreadInfo(nodeDefName + " Entered VTIDBResult.execute"); // Stack of objects that to access the back end data source. Stack<Object> sourceHandles = null; if ( isInMemRows ) { // InMemRows is set: DEAL WITH IN MEMORY ROWS CASE: sourceHandles = DataSourcesManager.getSourceHandlesPool( connectionDetails, true ); // No automated in-mem reload at present as we can't know when the underlying rdb table has changed. // However rows will have been reloaded if option INMEMORY has just been set in the config file for this node. // if ( isTableNeedsRefresh ) { // // Trigger was set off - or the rows were unloaded // loadInMemory(); // isTableNeedsRefresh = false; // } synchronized( sourceHandles ) { if ( !sourceHandles.empty() ) result = (InMemoryRows) sourceHandles.pop(); } if ( null == result ) { // No more instances of the InMemoryRows Class available result = new InMemoryRows(); logger.logThreadInfo( "Created a new InMemoryRows() instance as the Stack Pool was empty"); } else { logger.logThreadInfo( "Extracted an existing InMemoryRows() instance from the Stack Pool"); } // set rows and indexes ((InMemoryRows) result).setRowsAndIndexes( inMemoryRows, inMemoryRowsIndexes ); ((InMemoryRows) result).setExtractConditions( qualifiers, projectedColumns, safeExecNodeState.getColumnsMapping( (int[]) arguments.get(GaianTable.QRY_INCOMING_COLUMNS_MAPPING) ) ); if ( Logger.LOG_LESS <= Logger.logLevel ) { String prefix = Logger.sdf.format(new Date(System.currentTimeMillis())) + " ---------------> OBTAINED rows from: "; logger.logThreadImportant( nodeDefName + " OBTAINED ROWS USING:\n\n" + prefix + "InMemoryRows('" + getSourceDescription(null) + "')\n" ); } return result; } // Execution of a query against an rdbms source relies on us having obtained col types from the source: // For the Physical table directly: so that appropriate casts can be done when the logical types don't match. // For the InMemorRows: so that column mappings can be made against the physical cols. sourceHandles = DataSourcesManager.getSourceHandlesPool( connectionDetails ); // Use jdbc source handles Connection conn = null; Statement pstmt = null; try { // System.out.println("nodedef: " + nodeDefName + ", reenablement time: " + reenablementTime); if ( reenablementTime != 0 ) { if ( reenablementTime > System.currentTimeMillis() ) return null; reenablementTime = 0; } long t = 0; // See if we already have a retained resultSet (only the case when it HAD to be retrieved to get its meta-data) // *** WE WILL HAVE A RETAINED RESULT ON EACH INVOCATION OF A GAIANQUERY THAT WRAPS A CALL TO A STORED PROCEDURE *** // FOR REPEATED INVOCATIONS, WE STILL HAVE A NEW RETAINED RESULT EACH TIME - BECAUSE THE SHAPE OF A PROCEDURE RESULT CAN CHANGE ResultSet resultSet = logicalTableRSMD.getRetainedResultSet( nodeDefName ); Connection parentCalledProcedureConnection = logicalTableRSMD.getParentCalledProcedureConnection( nodeDefName ); int updateCount = logicalTableRSMD.getRetainedUpdateCount( nodeDefName ); logger.logThreadInfo(nodeDefName+" has a Retained resultSet/updateCount ? " + (null != resultSet) + "/" + (-2 < updateCount) ); // Check if we have a retained result if ( null == resultSet && -1 > updateCount ) { // No retained result - execute the query against the data source String sql = null; boolean isSubqCall = false; if ( isSubQuery ) { // This block of code analyzes pass-through sub-queries to: // 1. Determine if it is a stored procedure call - for later // 2. Abort it if it might perform a write and if propagated writes are disallowed // 3. Determine if the sub-query has nothing surrounding it, to by-pass later code setting selected cols or outer where-clause. String first7chars = 7 < tableExpression.length() ? tableExpression.substring(0, 7).toLowerCase() : null; if ( null != first7chars ) { // replace other whitespace (\\s) or double quotes (") with a space first7chars = first7chars.replaceAll("\\s", " ").replaceAll("\"", " "); isSubqCall = first7chars.startsWith("call ") || first7chars.startsWith("exec"); // Check if this is a straight sub-query - i.e. that it is not wrapped with brackets, e.g "(select * from t) SUBQ" // Note "select" or "update" tokens may be followed by a space ( ) or a double quote (") // If the expression is not bracketed then we don't need to wrap it ourselves (see GaianResult calling code)... if ( ! first7chars.startsWith("(") ) { // If this is anything other than a 'select' and was propagated (steps>0) and propagated writes are disallowed, do not execute. if ( !"select ".equals(first7chars) && !first7chars.startsWith("with ") && !first7chars.startsWith("values") && !first7chars.equals("xquery ") && 0 < ((Integer)arguments.get(GaianTable.QRY_STEPS)).intValue() && !GaianDBConfig.isAllowedPropagatedWrites() ) { logger.logImportant("Propagated write operation '" + first7chars.substring(0, first7chars.indexOf(' ')) + "' is not allowed. To enable it add this line to " + GaianDBConfig.getConfigFileName() + ": " + GaianDBConfig.ALLOW_PROPAGATED_WRITES + "=TRUE"); return null; } sql = tableExpression; // This is a non-bracketed sub-query - so there are no applicable external projected cols or qualifiers } } } // Just count the rows for back end db queries in explain mode logger.logThreadInfo(nodeDefName + " Args are: " + arguments.keySet()); // + " -> " + Arrays.asList( (String[]) arguments.keySet().toArray(new String[0]) ) ); String credentials = (String) arguments.get(GaianTable.QRY_CREDENTIALS); // only applicable for a propagation query to a gaian node if ( null == sql ) { // If we get here, we are not dealing with a plain sub-query (i.e. non-bracketed). Therefore, we may have projected cols and predicates to process. int[] pColTypes = safeExecNodeState.getPhysicalColTypes(); logger.logThreadInfo("Existing colTypes: " + Util.intArrayAsString(pColTypes)); if ( null == pColTypes && !isGaianNode ) { // && !isSubQuery ) { // NOTE! sub-queries need mappings for inner query resolved, e.g. select * from ( select * from abc ) t conn = getConnection( sourceHandles ); // if ( null == conn && 0 != reenablementTime ) { // logger.logThreadInfo( nodeDefName + " JDBC Connection is not available (yet) - returning null for this data source" ); // return null; // No connection as connection is not available (yet or not at all) // } final String bracketedSubQueryOrRawTableNameWithOptionalWhereClause = -1 == shorthandTableXprWhereClauseIndex ? tableExpression : tableExpression.substring(0, shorthandTableXprWhereClauseIndex); // crucial synchronous initialisation step: pColTypes = safeExecNodeState.getPhysicalColTypes(conn, bracketedSubQueryOrRawTableNameWithOptionalWhereClause); } final String[] pColNames = safeExecNodeState.getColNames(); int exposedColsCount = ((Integer)arguments.get(GaianTable.QRY_EXPOSED_COLUMNS_COUNT)).intValue(); String allProjectedColNames = !isGaianNode && arguments.containsKey(GaianTable.QRY_IS_EXPLAIN) ? "count(*)" : getColumnNamesAsCSV( pColNames, pColTypes, projectedColumns, exposedColsCount ); // DRV - 16/07/2012 - Don't do any explicit cast operations for MySQL as it is loosely typed. MySQL will do casting implicitly. // MySQL does not support casting to REAL, FLOAT, DOUBLE or NUMERIC (-> causes MySQL ERROR 1064). Only casts to DECIMAL work. // MySQL is actually loosely typed compared to other RDBMS providers. // Note that a RSMD resulting from a query on a MySQL table having several different column types returns columns all having column type = 1. // This means that our logic (in RowsFilter) would do a cast even when physical and logical types were defined as being the same (!). String sqlWhereClause = isGaianNode && arguments.containsKey(GaianTable.QRY_APPLICABLE_ORIGINAL_PREDICATES) ? (String) arguments.get(GaianTable.QRY_APPLICABLE_ORIGINAL_PREDICATES) : RowsFilter.reconstructSQLWhereClause( qualifiers, safeExecNodeState.getLogicalTableRSMD(), pColNames, rdbmsProvider, isGaianNode || isSubQuery || rdbmsProvider == RDBProvider.MySQL ? null : pColTypes ); //safeExecNodeState.getColTypes( statement, tableExpression ) ); qualifiers = null; // Qualifiers don't need to be applied again if ( 0 < sqlWhereClause.length() ) sqlWhereClause = ( -1 == shorthandTableXprWhereClauseIndex ? " WHERE (" : " AND (" ) + sqlWhereClause + ")"; if ( isGaianNode ) { sqlWhereClause += ( 0 < sqlWhereClause.length() ? " AND " : " WHERE " ) + GaianDBConfig.GDB_QRYID + "=? AND " + GaianDBConfig.GDB_QRYSTEPS + "=?" + // AND " + GaianDBConfig.GDB_QRYFWDER + "=?" ; ( null == credentials ? "" : " AND " + GaianDBConfig.GDB_CREDENTIALS + "=?" ); if ( arguments.containsKey(GaianTable.QRY_IS_GAIAN_QUERY) && ! tableExpression.endsWith(")") ) // Add column aliases to GaianQuery aliases... this allows us to select anonymous cols from a sub-query // DO NOT DO THIS for GaianTable because 1. col names always match so no need to, and 2. the column names may be in different positions // (we'd have to compute and use pColNames for GaianNode to change this) tableExpression += '(' + logicalTableRSMD.getColumnNamesIncludingNullOnes(exposedColsCount, true) + ')'; } else if ( isSubQuery && false == "1".equals(allProjectedColNames) ) // Add this to cater for non-ordinary column identifiers, e.g. "1" tableExpression += '(' + Util.stringArrayAsCSV(safeExecNodeState.getPhysicalColumnNames(), rdbmsProvider) + ')'; // System.out.println("tableExpression: " + tableExpression + ", whereClause: " + sqlWhereClause); sql = "select " + allProjectedColNames + " from " + tableExpression + sqlWhereClause; } String sqlOrderBy = isGaianNode ? "" : (String) arguments.get(GaianTable.QRY_ORDER_BY_CLAUSE); if ( null == sqlOrderBy ) sqlOrderBy = ""; if ( 0 < sqlOrderBy.length() ) sqlOrderBy = " " + sqlOrderBy; sql += sqlOrderBy; Integer timeout = (Integer) arguments.get( GaianTable.QRY_TIMEOUT ); // Only push gaiandb hint information for queries destined to 1) other gaiandb nodes or 2) the local derby(/gaiandb). // Note it would be incorrect to use ( isGaianNode || isSubQuery ) because sub-queries may target other RDBMS which may not accept the same hint syntax // Also, queries may be generated for sources of logical tables against the local derby (not just sub-queries) - and these may themselves have logical table references. if ( isGaianNode || isLocalDerbyDataSource ) { sql += " -- "+GaianTable.GDB_HASH+"="+arguments.get(GaianTable.QRY_HASH); // there should *always* be a QRY_HASH // Note GDB_CREDENTIALS is read via a positional parameter if isGaianNode is true (i.e. for propagated queries - see also GaianTable.java near line 985) if ( false == isGaianNode && null != credentials ) sql += " "+SecurityManager.CREDENTIALS_LABEL+"="+credentials; if ( arguments.containsKey(GaianTable.QRY_WID) ) sql += " "+GaianTable.GDB_WID+"="+arguments.get(GaianTable.QRY_WID); if ( null != timeout ) sql += " "+GaianTable.GDB_TIMEOUT+"="+timeout; } if ( null == conn ) conn = getConnection( sourceHandles ); // if ( null == conn && 0 != reenablementTime ) { // logger.logThreadInfo( nodeDefName + " JDBC Connection is not available (yet) - returning null for data source" ); // return null; // No connection as connection is not available (yet or not at all) // } // Keep track of ordered active connections in order to find any potential hanging ones later on.. // The hanging one will be the oldest. activeConnections.push( conn ); logger.logThreadInfo(nodeDefName + " Active connection added: " + conn); Map<String, PreparedStatement> preparedStatements = preparedStatementsOfConnections.get( conn ); if ( null == preparedStatements ) { // No prepared statements for this connection at all yet! - // Create a CachedHashMap for them which starts closing them if it gets too big... preparedStatements = new CachedHashMap<String, PreparedStatement>(PREPARED_STMTS_CACHE_SIZE_PER_CONNECTION) { private static final long serialVersionUID = 1L; protected boolean removeEldestEntry(Map.Entry<String, PreparedStatement> eldest) { final boolean isSizeSurpassed = super.removeEldestEntry(eldest); if ( isSizeSurpassed ) { PreparedStatement pstmt = eldest.getValue(); if ( null != pstmt ) try { pstmt.close(); } catch ( SQLException e ) {} } return isSizeSurpassed; }; }; preparedStatementsOfConnections.put( conn, preparedStatements ); } // NOTE: Unresolved issue: // DROP statements cannot run concurrently with any other statement. This throws a SQLException. // The fundamental problem is that Derby closes its resources lazily. We see that through lazy calls to GaianTable.close(). // This comes up as an intermittent test failure with Test_workedExamplesTwo.java pstmt = preparedStatements.get(sql); boolean isPrepared = null != pstmt; logger.logThreadInfo("Number of PreparedStatements for Connection: " + preparedStatements.size() + ", SQL is prepared? " + isPrepared); if ( isSubQuery ) // Preparing complex sub-queries having nested joins requires derby to cache sub-result "conglomerates". // This is seemingly not implemented by derby for vtis (yet) - as we hit "conglomerate does not exist" errors. // Therefore we don't prepare sub-queries... we just execute them explicitly each time. pstmt = conn.createStatement(); else if ( !isPrepared ) { String prefix = Logger.sdf.format(new Date(System.currentTimeMillis())) + " ---------------> PREPARING against " + nodeDefName + ": "; logger.logThreadImportant( nodeDefName + " PREPARING:\n\n" + prefix + sql + "\n" ); pstmt = conn.prepareStatement(sql); preparedStatements.put(sql, ((PreparedStatement) pstmt)); } if ( isGaianNode ) { ((PreparedStatement) pstmt).setString( 1, (String) arguments.get(GaianTable.QRY_ID)); ((PreparedStatement) pstmt).setInt( 2, ((Integer) arguments.get(GaianTable.QRY_STEPS)).intValue()+1 ); if ( null != credentials ) ((PreparedStatement) pstmt).setString( 3, credentials ); // pstmt.setString( 3, (String) arguments.get(GaianTable.QRY_FWDER)); } // logger.logThreadDetail("Statement: " + statement + ", Connection: " + (null==statement?null:statement.getConnection())); // logger.logThreadDetail("Connection is closed: " + (null==statement?true:statement.getConnection().isClosed())); if ( null == timeout ) { if ( !GaianNode.IS_UDP_DRIVER_EXCLUDED_FROM_RELEASE && isGaianNode && GaianNode.isNetworkDriverGDB() ) { // Early hack For UDP - Reduce timeout depending on depth, so top level client doesn't timeout before partial results get back int depth = ((Integer) arguments.get(GaianTable.QRY_STEPS)).intValue(); timeout = Math.max( 1000, GaianDBConfig.getNetworkDriverGDBUDPTimeout()-depth*1000 )/1000; pstmt.setQueryTimeout( timeout ); // Use UDP network driver timeout } // logger.logThreadInfo("Setting JDBC Statement execution timeout to Default:" + 10); } else { logger.logThreadInfo(nodeDefName + " Setting JDBC Statement execution timeout to " + timeout); pstmt.setQueryTimeout( timeout.intValue() ); } //statement.setFetchSize(10000); if ( Logger.LOG_NONE < Logger.logLevel ) { String prefix = Logger.sdf.format(new Date(System.currentTimeMillis())) + " ---------------> EXECUTING against " + nodeDefName + ": "; logger.logThreadImportant( nodeDefName + " EXECUTING against connection:\n\n" + prefix + sql + (isGaianNode ? " ; qryid: " + arguments.get(GaianTable.QRY_ID) + ", steps: " + (((Integer) arguments.get(GaianTable.QRY_STEPS)).intValue()+1) : "") + "\n" ); // + ", fwder: " + arguments.get(GaianTable.QRY_FWDER) + "\n" ); t = System.currentTimeMillis(); } if ( isSubQuery ) pstmt.execute(sql); else ((PreparedStatement) pstmt).execute(); // can be any query - not just a select if ( ! (pstmt instanceof GaianChildVTI) ) { resultSet = pstmt.getResultSet(); if ( null == resultSet ) { // isSubQuery must also be true.. because we only run CRUD or CALLs in sub-queries updateCount = pstmt.getUpdateCount(); logger.logThreadInfo("Closing pstmt and Recycling JDBC Connection early because result is an Update Count"); // This must be a sub-query (as CRUD and CALLs only occur in them) - so pstmt will not be cached - so close it now to free client+server resources try { pstmt.close(); } catch ( SQLException e ) {} if ( false == recycleSourceHandleToPool( conn ) ) try { conn.close(); } catch ( SQLException e ) {} // pool is probably maxed out - close resource. } } // Record the parent connection for recycling (later), because the result is based off a separate connection spawned in the procedure... if ( isSubqCall ) { // this MUST NOT be recycled now... because a wrapping GaianQuery may re-use the connection before we've fetched rows.. this causes a locking condition.. parentCalledProcedureConnection = conn; logger.logThreadInfo("Recorded parent Connection for executed nested stored procedure - to be recycled later"); } } if ( ! (pstmt instanceof GaianChildVTI) ) { if ( null == resultSet ) { // This was one of insert/update/delete/call/create/drop // The outer connection should be recycled already - whether the update count was obtained here or was in a retained result from a SP (resolved in DataSourcesManager). result = new GaianChildRSWrapper( updateCount ); } else // Note most queries generate a resultSet which is linked directly to the parent connection to be recycled - // The exception to this rule is for resultSets that are derived from a child connection created inside a stored procedure. // In that latter case, we need to remember the parent connection used to execute the SP and recycle that one at the end. result = new GaianChildRSWrapper( resultSet, parentCalledProcedureConnection ); // either a select or a call() returning a resultSet } else result = (GaianChildVTI) pstmt; // Already implements our interface - no need to wrap if ( Logger.LOG_NONE < Logger.logLevel ) rdbExecTimes.put( result, new Long( System.currentTimeMillis() - t ) ); // Low value feature to skip maintenance for certain connections // if ( isGaianNode ) DatabaseConnectionsChecker.excludeConnectionFromNextMaintenanceCycle( // nodeDefName.substring(nodeDefName.lastIndexOf('_')+1)); if ( null != qualifiers ) logger.logInfo(nodeDefName + " setExtractConditions() setting qualifiers to: " + RowsFilter.reconstructSQLWhereClause(qualifiers)); result.setExtractConditions( qualifiers, projectedColumns, isGaianNode ? null : safeExecNodeState.getColumnsMapping( (int[]) arguments.get(GaianTable.QRY_INCOMING_COLUMNS_MAPPING) ) ); return result; } catch ( SQLException e ) { // Low value feature to skip maintenance for certain connections // String eDigest = Util.getAllExceptionCauses(e); // if ( -1 != eDigest.indexOf(GaianTable.REVERSE_CONNECTION_NOT_ESTABLISHED_ERROR) ) { // logger.logWarning(nodeDefName + " Query Failure (dropping gaian connection): " + eDigest); // lostConnection(); // return null; // } if ( null == pstmt ) { // e.printStackTrace(); final String iex = Util.getGaiandbInvocationTargetException(e); logger.logThreadWarning( GDBMessages.ENGINE_STATEMENT_PREPARE_ERROR_SQL, nodeDefName + " Unable to PREPARE statement - (empty result for this data source): " + e.getMessage() + (null==iex?"":" Root cause: "+iex) + getRdbmsProviderSpecificInfo(e) + " - 'call listrdbc()' to identify the data source"); // Changed condition below... If the connection is null this may just be because we cant obtain one quickly enough - // Don't drop the entire gaian connection as a result of this. // if ( null == conn || conn.isClosed() ) lostConnection(); if ( null == conn ) { // We can't obtain a connection fast enough - disable this rdbms node temporarily to avoid continuous re-tries if ( isGaianNode ) { String cid = nodeDefName.substring( nodeDefName.lastIndexOf('_') + 1 ); if ( GaianDBConfig.isDiscoveredConnection(cid) ) return null; if ( GaianDBConfig.isDefinedConnection(cid) ) logger.logThreadWarning(GDBMessages.ENGINE_GATEWAY_UNREACHABLE, nodeDefName + " Unreachable Gateway: " + cid + " (can slow down queries) - to remove, run: call gdisconnect('" + cid + "')"); else logger.logThreadWarning(GDBMessages.ENGINE_GAIAN_CONN_NOT_REGISTERED, nodeDefName + " Undefined connection id: " + cid + ". Disabled data source and associated left over connection were erroneously used (purge could be in process)"); } else logger.logThreadWarning(GDBMessages.ENGINE_DS_RDBMS_UNDREACHABLE, nodeDefName + " Unreachable RDBMS in Data Source: " + nodeDefName + " (can slow down queries) - to remove, run: call removeds('" + nodeDefName + "')"); // Disable temporarily to save resources temporarilyDisable(); } else { // if ( null != conn ) { if ( conn.isClosed() ) lostConnection(); else { // Connection is still active and Statement is ok - recycle it and return null if ( false == recycleSourceHandleToPool( conn ) ) { // We didn't recycle this connection (due to pool being maxed out), so close it - // Ignore exceptions (e.g. if the connection reference has already gone) try { conn.close(); } catch ( SQLException e1 ) {} } } } return null; } else { // Statement EXECUTION failed - we need to determine whether the connection has been lost... // Assume connection is invalid unless proven otherwise // Note: statement.getConnection() throws an exception if the connection has been lost boolean isConnectionInvalid = true; try { isConnectionInvalid = pstmt.getConnection().isClosed(); } catch ( SQLException e1 ) { logger.logThreadDetail("Unable to check connection validity with pstmt.getConnection().isClosed() (ignored): " + e1); if ( -1 < e1.getMessage().indexOf("Method not supported") ) isConnectionInvalid = false; // we don't know if connection is ok.. } if ( isConnectionInvalid ) { logger.logThreadWarning(GDBMessages.ENGINE_STATEMENT_EXEC_JDBC_CONN_ERROR, nodeDefName + " Statement execution failed due to lost jdbc connection (returning null): " + e); lostConnection(); return null; } final String iex = Util.getGaiandbInvocationTargetException(e); logger.logThreadWarning( GDBMessages.ENGINE_STATEMENT_EXEC_ERROR, nodeDefName + " Unable to EXECUTE statement (returning null result): " + Util.getStackTraceDigest(e) + (null==iex?"":" Root cause: "+iex) ); // Connection is still active and pstmt must be closed (as it failed) - recycle connection and return null if ( false == recycleSourceHandleToPool( conn ) ) { // We didn't recycle this connection (due to pool being maxed out), so close it - // Ignore exceptions (e.g. if the connection reference has already gone) try { conn.close(); } catch ( SQLException e1 ) {} } return null; } } finally { if ( !isInMemRows && null != pstmt ) { activeConnections.remove( conn ); // note we can't pop in case of concurrent threads!! logger.logThreadInfo(nodeDefName + " Active connection removed: " + conn); } } } private static final String DB2_DRIVER = "com.ibm.db2.jcc.DB2Driver"; private String getRdbmsProviderSpecificInfo( SQLException e ) { if ( null != connectionDetails && connectionDetails.startsWith( DB2_DRIVER ) ) return Util.getDB2Msg(e, false); return ""; } // private ResultSet execute( Statement stmt, String sql ) throws SQLException { // // For executions against other GaianNodes, use a watchdog to ensure the execution doesn't hang due to network problems... //// return isGaianNode ? new StatementExecutor(false).execute(stmt, sql) : stmt.executeQuery( sql ); // return stmt.executeQuery( sql ); // } public void lostConnection() { if ( isGaianNode ) { // Let the config know that the connection is lost - if it is a system discovered connection it will be removed // from config and config will be reloaded, thus purging stale connections and vtis. // If it is not a system discovered connection, then just clear the stack pool (closing statements/connections within). // VTIs based on the connection id will still try to obtain connections for it after the re-enablement time String cid = nodeDefName.substring( nodeDefName.lastIndexOf('_') + 1 ); if ( GaianDBConfig.isDiscoveredConnection(cid) ) { GaianNodeSeeker.lostDiscoveredConnection(cid); return; } logger.logThreadWarning(GDBMessages.ENGINE_GATEWAY_UNREACHABLE_CONN_LOST, nodeDefName + " Unreachable gateway: " + cid + " (can slow down queries) - To disable, run: call gdisconnect('" + cid + "')"); } else logger.logThreadWarning(GDBMessages.ENGINE_DS_RDBMS_UNDREACHABLE_CONN_LOST, nodeDefName + " Unreachable RDBMS in data source: " + nodeDefName + " (can slow down queries) - To disable, run: call removeds('" + nodeDefName + "')"); DataSourcesManager.clearSourceHandlesStackPool( connectionDetails ); temporarilyDisable(); } private void temporarilyDisable() { temporarilyDisable(DISABLEMENT_PERIOD_MS); } public void temporarilyDisable( int millis ) { reenablementTime = System.currentTimeMillis() + millis; } public void reEnableNow() { reenablementTime = 0; } // public Connection getConnectionOfLongestRunningStatement() { // // We are looking for a potentially broken and hanging connection, but it may be that the connections pool contains // // connections that have been established after the one that was broken... Therefore we target the longest running statement, // // knowing that if that one is still active (i.e. a long running query), then all subsequent ones should be active as well. // logger.logDetail(nodeDefName + " Concurrent running statements: " + activeConnections.size()); // try { return (Connection) activeConnections.lastElement(); } // catch ( Exception e ) { return null; } // } public Connection getConnectionFromApplicablePool() throws SQLException { return getConnection( DataSourcesManager.getSourceHandlesPool( connectionDetails ) ); } public void returnConnectionToApplicablePool(Connection c) throws SQLException { DataSourcesManager.getSourceHandlesPool( connectionDetails ).push(c); } private Connection getConnection( Stack<Object> sourceHandles ) throws SQLException { Connection connection = null; synchronized( sourceHandles ) { if ( !sourceHandles.empty() ) connection = (Connection) sourceHandles.pop(); } if ( null != connection ) { logger.logThreadInfo( "Extracted an existing DB Handle from the Stack Pool" ); if ( sourceHandles.empty() ) { logger.logThreadInfo( "Getting another one asynchronously as pool is now empty..." ); DataSourcesManager.getRDBHandleInSeparateThread( connectionDetails, sourceHandles ); } try { // Check if connection was closed by JDBC for whatever reason and explicitly throw an SQLException if it was. if ( connection.isClosed() ) { throw new SQLException("Connection closed"); } } catch ( SQLException e ) { logger.logInfo(e.getMessage()); lostConnection(); throw e; } } else { // The pool is originally primed so this code should only be reached when query // throughput exceeds the rate at which connections can be created. // However - even if we can't get a connection fast enough here, this does not // mean the source is unavailable and MUST DEFINITELY NOT trigger a lostConnection() // to be sent to the GaianNodeSeeker... // Don't try to get another connection if the node is disabled temporarily // if ( 0 == reenablementTime || reenablementTime < System.currentTimeMillis() ) { // reenablementTime = 0; logger.logThreadInfo( "Ran out of connections! Trying to get 2 (1 + 1 spare)..." ); connection = DataSourcesManager.getRDBHandleQuickly( connectionDetails, sourceHandles ); // And get another one as a spare DataSourcesManager.getRDBHandleInSeparateThread( connectionDetails, sourceHandles ); // } } return connection; } /** * TODO * This method should call out directly to DatabaseConnector.getConnectionWithinTimeoutOrToPoolAsynchronously() * Need to replace the getConnection() code in this class to get pooled Connection objects using the method: getPooledSourceHandle(), which calls this one. */ @Override protected Object getNewSourceHandleWithinTimeoutOrToSourcesPoolAsynchronously() throws Exception { return null; // currently not used - we just get connections from the pool using getConnection() } /** * Gets a comma separated String of column name mappings that would be inserted into the select * section of an SQL statement, e.g: "physicalcol1, physicalcol2, ..." * Each column name will either have an explicit mapping to a different physical column name, or it * will have no mapping, in which case the logical column name is used. * The column name may also have lower-case or special characters, in which case it will be wrapped in RDBMS-appropriate delimiters. * * @param pColNames: Physical column names for each logical column index * @param pColTypes: Physical column types * @param projectedColumns: Array of 1-based indexes of logical table columns involved in the query * @param exposedColCount: * @return String of column names appropriate for the SELECT list of the SQL query. */ private String getColumnNamesAsCSV( final String[] pColNames, final int[] pColTypes, final int[] projectedColumns, final int exposedColCount ) { if ( null == projectedColumns ) // Only applicable for queries on subqueries which are NOT themselves 'select *' return "*"; int colCount = projectedColumns.length; // Deal with the count(*) query case - Derby expects us to return an empty row for each record that matches the query // By returning "1" here we will pull out a minimal row for every record... // [Note: in future it might be better to translate this to a count(*) instead and return true in GaianChildRSWrapper.nextRow() // the number of times given by the result of the count (but this means changing all data sources i.e. FileImport and // InMemoryRows to also cycle through rows differently for a count(*))... remember this must be different for an explain // query where we just use the value of the count(*) as a direct result to be added to the aggregated count.] if ( 0 == colCount ) return "1"; logger.logThreadInfo("Getting columns list to query, columns count = " + colCount + ", pColNames = " + Arrays.asList(pColNames) ); // System.out.println("colCount: " + colCount + ", pcolnames: " + Arrays.asList(pColNames)); GaianResultSetMetaData ltrsmd = safeExecNodeState.getLogicalTableRSMD(); StringBuffer csvColsForSelect = new StringBuffer(); for ( int i=0; i<colCount; i++ ) { int colIndex = projectedColumns[i]-1; String columnName; if ( colIndex < pColNames.length ) { if ( safeExecNodeState.isColumnMissingInPhysicalSource(colIndex) ) continue; // skip this column - we wont pull anything out of the db for it. We will replace with null when fetching. // The column is physical and exists in the physical table, so colNames will hold the correct mapped name. columnName = pColNames[ colIndex ]; // Column was mapped to a non-null value, so select this column. } else // Otherwise, for other logical cols (constant or hidden cols), just use the name in the logical table's meta data. columnName = ltrsmd.getColumnName( colIndex+1, exposedColCount ); // String columnName = colIndex < pColNames.length ? // pColNames[ colIndex ] : ltrsmd.getColumnName( colIndex+1, exposedColCount ); // logger.logInfo("Got column name " + columnName + " for colindex " + colIndex + ", isColumnMapped = " // + safeExecNodeState.isColumnMappingExplicitlyDefinedInConfig(colIndex)); String colXpr = null; if ( !isGaianNode && !isSubQuery && safeExecNodeState.isColumnMappingExplicitlyDefinedInConfig(colIndex) ) colXpr = columnName; else { colXpr = GaianResultSetMetaData.wrapColumnNameForQueryingIfNotAnOrdinaryIdentifier(columnName, rdbmsProvider); if (!isGaianNode && null != pColTypes) { String colXprNormalised = RowsFilter.applyProviderNormalisationTransformIfNecessary(colXpr, pColTypes[i], rdbmsProvider); if ( false == colXpr.equals(colXprNormalised) ) colXpr = colXprNormalised + " " + colXpr; // Use alias to re-assign original col name } } csvColsForSelect.append( (0==i?"":", ") + colXpr ); } return csvColsForSelect.toString(); } public void tableNeedsRefresh() { // isTableNeedsRefresh = true; } GaianChildVTI getAllRows() throws SQLException { // return DataSourcesManager.getRDBHandle( connectionDetails ).executeQuery( "select * from " + table ); logger.logThreadInfo( nodeDefName + " Getting all rows from table: " + physicalTable ); Stack<Object> sourceHandles = DataSourcesManager.getSourceHandlesPool( connectionDetails ); Connection c = null; // Repeat some code from getStatement() as we don't want to impose a time limit on getting it. synchronized( sourceHandles ) { if ( !sourceHandles.isEmpty() ) { c = (Connection) sourceHandles.pop(); try { if ( c.isClosed() ) throw new SQLException("Connection closed"); } catch ( SQLException e ) { lostConnection(); throw e; } } else { logger.logThreadInfo( nodeDefName + " getAllRows() requesting new RDBMS connection for: " + connectionDetails ); c = GaianDBConfig.getNewDBConnector( GaianDBConfig.getConnectionTokens(connectionDetails) ).getConnection(); } } return new GaianChildRSWrapper( c.createStatement().executeQuery( "select * from " + physicalTable ) ); } public long removeRDBExecTime( GaianChildVTI nodeRows ) { Long execTime = (Long)rdbExecTimes.remove( nodeRows ); if ( null==execTime ) { if ( !isRowsInMemory ) logger.logWarning( GDBMessages.ENGINE_JDBC_EXEC_TIME_UNRECORDED, nodeDefName + " JDBC Execution time was not recorded for a ResultSet (ignored)" ); return -1; } return execTime.longValue(); } public boolean isBasedOn( String s ) { return s.equals( connectionDetails ); } public String getSourceDescription( String dsInstanceID ) { if ( null == dsInstanceID ) dsInstanceID = physicalTable; return GaianDBConfig.getConnectionTokens(connectionDetails)[1] + ( null==dsInstanceID ? "" : "::" + dsInstanceID ); } public void recycleOrCloseResultWrapper( GaianChildVTI rows ) throws Exception { logger.logThreadInfo("Entered recycleOrCloseResultWrapper(), rows instanceof InMemoryRows? " + (rows instanceof InMemoryRows)); if ( rows instanceof InMemoryRows ) { rows.reinitialise(); recycleSourceHandleToPool( rows ); return; } logger.logThreadInfo("rows instanceof IFastPath? " + (rows instanceof IFastPath)); // The distinction between IFastPath and GaianChildVTI is subtle. // An IFastPath is an executable statement with easy row access, but its rows are tightly coupled with the statement. // A GaianChildVTI is a wrapper for an external data source result - which is easily accessible. The external result may need releasing. if ( rows instanceof IFastPath ) { // true for example if rows are a LightPreparedStatement recycleSourceHandleToPool( ((Statement) rows).getConnection() ); return; } Statement s = null; logger.logThreadInfo("Checking Statement for cleanup"); // + (s = ((GaianChildRSWrapper) rows).getStatementForCleanup())); // Statement will be null if an update count was derived on a callable statement. try { if ( null == ( s = ((GaianChildRSWrapper) rows).getStatementForCleanup() ) ) return; } catch ( SQLException e ) { return; } Connection c = s.getConnection(); rows.close(); // don't close the statement as well as it may be a prepared statement we want to re-use.. if ( c.isClosed() ) { logger.logThreadInfo("Connection is closed (it was possibly spawned in a procedure call) - nothing to recycle"); return; } logger.logThreadInfo("Recycling JDBC Connection associated with ResultSet"); // If this VTIRDBResult is a sub-query, then it's statements will not be cached - so clear this one now to clear down client+server resources if ( isSubQuery ) try { s.close(); logger.logThreadInfo("Closed sub-query statement (because they are not reused)"); } catch ( SQLException e ) { logger.logThreadInfo("Unable to close() sub-query statement (ignored), cause: " + e); } if ( false == recycleSourceHandleToPool( c ) ) // We didn't recycle this connection (due to pool being maxed out), so close it - // Ignore exceptions (e.g. if the connection reference has already gone) try { c.close(); logger.logThreadInfo("Closed connection (could not recycle as connection pool was full"); } catch ( SQLException e ) { logger.logThreadInfo("Unable to close() connection after finding pool was maxed out (ignored), cause: " + e); } } // public void recycleResult( GaianChildVTI rows ) throws SQLException { // // boolean isInMemoryRows = rows instanceof InMemoryRows; // // Statement will be null if an update count was derived on a callable statement. // try { // if ( !isInMemoryRows && !(rows instanceof IFastPath) && null == ((GaianChildRSWrapper) rows).getStatement() ) // return; // } catch ( SQLException e ) { return; } // // // Get the wrapping resource handle which we will want to recycle after closing the rows. // Object rsrc = isInMemoryRows ? (Object) rows : //(Object) ((GaianChildRSWrapper) rows).getStatement().getConnection(); // (Object) ( (Statement) ( rows instanceof IFastPath ? rows : ((GaianChildRSWrapper) rows).getStatement() ) ).getConnection(); // // rows.close(); // // if ( rsrc instanceof Connection ) { // if ( ((Connection) rsrc).isClosed() ) { // logger.logThreadInfo("Connection is closed (it was possibly spawned in a procedure call) - nothing to recycle"); // return; // } // logger.logThreadInfo("Recycling JDBC Connection associated with ResultSet"); // } // // if ( false == genericRecycleResult( rsrc ) && ! isInMemoryRows && !(rows instanceof IFastPath) ) // // We didn't recycle this connection (due to pool being maxed out), so close it - // // Ignore exceptions (e.g. if the connection reference has already gone) // // try { Statement s = ((GaianChildRSWrapper) rows).getStatement(); Connection c = s.getConnection(); s.close(); c.close(); } // catch ( SQLException e ) {} // // // Make this transient node available for re-use //// if ( isSubQuery || nodeDefName.startsWith( DataSourcesManager.GATEWAY_PREFIX ) ) //// DataSourcesManager.recycleTransientNode( nodeDefName, this ); // } // Pattern used to guard against WHERE expressions which are nested in GaianQuery VTIs. // Limitation: this will match when we wouldn't want it to: ' ) T where (g>'s') group by g // So we have to disallow special end clauses in the docs: GROUP|ORDER|FETCH|FOR|OFFSET|WITH private static final Pattern patternEndVTIExpression = Pattern.compile("(?i)'\\s*\\)\\s*(?:AS\\s+)?[A-Z]\\w*(?:\\s+.*)?"); public void customReinitialise() throws SQLException { isGaianNode = false; isSubQuery = nodeDefName.startsWith( DataSourcesManager.SUBQUERY_PREFIX ); activeConnections.clear(); logger.logThreadDetail(nodeDefName + " Active connections cleared"); String ptDef = GaianDBConfig.getNodeDefRDBMSTable( nodeDefName ); String vtiClassName = GaianDBConfig.getNodeDefVTI( nodeDefName ); if ( null != ptDef && !isSubQuery ) { ptDef = ptDef.trim(); // ptDef = ptDef.toUpperCase(); //Sybase is case-sensitive, so leave the table definition as-is. // Search for WHERE clause in physical table definition if ( !ptDef.startsWith("(") ) { // don't look to protect a where-clause if the table expression is defined as a subquery against the physical db shorthandTableXprWhereClauseIndex = ptDef.toUpperCase().lastIndexOf(" WHERE "); if ( -1 != shorthandTableXprWhereClauseIndex ) { // Guard against situations of having a WHERE clause being wrongly detected from inside a sub-query... int gqIndex = ptDef.toUpperCase().indexOf("COM.IBM.DB2J.GAIANQUERY"); int lastQuoteIndex = ptDef.lastIndexOf('\''); if ( -1 != gqIndex && gqIndex < shorthandTableXprWhereClauseIndex && lastQuoteIndex > shorthandTableXprWhereClauseIndex && patternEndVTIExpression.matcher( ptDef.substring(lastQuoteIndex) ).matches() ) shorthandTableXprWhereClauseIndex = -1; // do not try to extract this as a WHERE clause later if ( -1 != shorthandTableXprWhereClauseIndex ) { StringBuilder buf = new StringBuilder( ptDef ); buf.insert(shorthandTableXprWhereClauseIndex+7, '('); buf.append(')'); ptDef = buf.toString(); } } } else shorthandTableXprWhereClauseIndex = -1; logger.logInfo( nodeDefName + " table def = " + ptDef ); // + ", vticlass def = " + vtiClassName ); // If the table definition has just changed and this source is in memory, then it needs // reloading now - so clear it out so it can be reloaded // Note that if isRowsInMemory is false, it would mean the setting has been cleared, // so we'll clear the in-memory rows below anyway. if ( !ptDef.equals(physicalTable) && isRowsInMemory && null != inMemoryRows ) { logger.logInfo(nodeDefName + " Clearing in-memory rows and indexes for reload as table def has changed" ); clearInMemoryRowsAndIndexes(); isRowsInMemory = true; // set the flag again to indicate the rows need reloading } } physicalTable = ptDef; if ( !isSubQuery && null == physicalTable ) { if ( null == vtiClassName ) { // No table or vti definitions. This must be a query to another Derby node. // We can't define table yet as we don't know the query id or propagation count isGaianNode = true; } else { // Deprecated method of accessing VTIs... // This is a VTI - accessed through a DB connection try { if ( Class.forName(vtiClassName).getName().equals( FileImport.class.getName() ) ) { String vtiArgs = GaianDBConfig.getVTIArguments(nodeDefName); if ( null == vtiArgs ) //|| 0 == vtiArgs.length ) throw new SQLException("Undefined ARGS property for Node"); physicalTable = "NEW " + vtiClassName + "('" + vtiArgs + "') AS " + nodeDefName; } else { logger.logWarning( GDBMessages.ENGINE_VTI_CLASS_UNSUPPORTED, nodeDefName + " Unsupported VTI class: " + vtiClassName ); } } catch ( ClassNotFoundException e ) { logger.logWarning( GDBMessages.ENGINE_VTI_CLASS_NOT_FOUND, nodeDefName + " Class not found: " + vtiClassName ); } } } if ( !isGaianNode ) // inMemory option is not applicable to gaian data sources and we want to avoid logging anyway as nodes can connect/disconnect often logger.logThreadInfo( nodeDefName + " inMemoryRows is null: " + (null==inMemoryRows) + ", isNodeInMemoryOptionSet: " + isRowsInMemory ); if ( null == inMemoryRows && isRowsInMemory ) { if ( isGaianNode ) { logger.logWarning( GDBMessages.ENGINE_DBMS_ROWS_LOAD_ERROR, "Cannot load DBMS rows in memory for remote GaianNode - ignoring INMEMORY option" ); isRowsInMemory = false; // } else if ( !isLocalDerbyTable ) { // logger.logWarning( "Cannot load DBMS rows in memory for non Derby database - ignoring INMEMORY option" ); // isRowsInMemory = false; } else if ( isSubQuery ) { logger.logWarning( GDBMessages.ENGINE_DBMS_ROWS_LOAD_SUBQ_ERROR, "Cannot load DBMS rows in memory for subqueries - ignoring INMEMORY option" ); isRowsInMemory = false; } else { loadRowsInMemoryAsynchronously(); // try { // loadInMemory(); // } catch (Exception e) { // logger.logWarning( "Unable to load rows in memory: " + e ); // isRowsInMemory = false; // clearInMemoryRowsAndIndexes(); // } // // Create triggers // Statement stmt = DataSourcesManager.getRDBStatement( connectionDetails ); // for (int i=0; i<TRIGEVENTS.length; i++) { // // String trig = TRIGEVENTS[i]; // String triggerSQL = // "CREATE TRIGGER " + table.toUpperCase() + "_" + trig + " AFTER " + trig + " ON " + table + // " FOR EACH STATEMENT MODE DB2SQL CALL GAIANDB_TABLE_CHANGED('" + nodeDefName + "')"; // // logger.logInfo( nodeDefName + " Creating trigger using SQL: " + triggerSQL); // stmt.execute( triggerSQL ); // logger.logInfo( nodeDefName + " Created trigger: " + table.toUpperCase() + "_" + trig ); // } } } else if ( !isRowsInMemory && null != inMemoryRows ) { logger.logInfo( nodeDefName + " Clearing in-memory rows and indexes" ); clearInMemoryRowsAndIndexes(); // // Drop triggers // if ( !isGaianNode && isLocalDerbyTable ) { // // Statement stmt = DataSourcesManager.getRDBStatement( connectionDetails ); // for (int i=0; i<TRIGEVENTS.length; i++) { // // String trig = TRIGEVENTS[i]; // String triggerSQL = "DROP TRIGGER " + table.toUpperCase() + "_" + trig; // logger.logInfo( nodeDefName + " Dropping trigger using SQL: " + triggerSQL); // stmt.execute( triggerSQL ); // logger.logInfo( nodeDefName + " Dropped trigger: " + table.toUpperCase() + "_" + trig ); // } // } } Stack<Object> sourceHandles = DataSourcesManager.getSourceHandlesPool( connectionDetails, isRowsInMemory ); if ( isRowsInMemory ) { if ( sourceHandles.empty() ) { sourceHandles.push( new InMemoryRows() ); logger.logInfo( nodeDefName + " Created initial InMemory rows for Stack Pool"); } else logger.logInfo( nodeDefName + " Initial InMemory rows already exist in Stack Pool"); } // else { // // Refresh column mappings, because in-memory rows being loaded asynchronously will rely on them once loaded // // and they may not have been set in the safeExecNodeState as this is done in this thread when returning from this method. // --->> This is now done as part of setColTypes() for InMemoryRows of RDBMS sources as well as non-InMemory ones. // --->> We shdnt be checking the physical data source and refreshing col mappings with it here because the data source // --->> may only become available way after initialisation... // Statement s = null; // // // Repeat some code from getStatement() as we don't want to impose a time limit on getting it. // synchronized( sourceHandles ) { // if ( !sourceHandles.isEmpty() ) { // s = (Statement) sourceHandles.pop(); // try { if ( s.getConnection().isClosed() ) throw new SQLException("Connection closed"); } // catch ( SQLException e ) { lostConnection(); throw e; } // } else // s = GaianDBConfig.getNewDBConnector( GaianDBConfig.getConnectionTokens(connectionDetails) ).getStatement(); // } // physicalTableRSMD = // s.getConnection().prepareStatement( "select * from " + table ).getMetaData(); // // if ( sourceHandles.empty() ) { // // try to get a statement now, but nevermind if we can't // try { // // sourceHandles.push( getStatement( sourceHandles ) ); // logger.logInfo( nodeDefName + " Created initial JDBC Connection" ); // } catch (SQLException e) { // logger.logWarning( nodeDefName + " Currently unable to obtain initial Connection: " + e.getMessage()); // } // } else // logger.logInfo( nodeDefName + " Initial JDBC Connection already obtained in Stack"); // } } }