/*
* (C) Copyright IBM Corp. 2011
*
* LICENSE: Eclipse Public License v1.0
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.ibm.gaiandb.lite;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;
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 com.ibm.db2j.VTI60;
import com.ibm.gaiandb.DataSourcesManager;
import com.ibm.gaiandb.DatabaseConnectionsChecker;
import com.ibm.gaiandb.GaianChildVTI;
import com.ibm.gaiandb.GaianDBConfig;
import com.ibm.gaiandb.GaianDBUtilityProcedures;
import com.ibm.gaiandb.GaianResultSetMetaData;
import com.ibm.gaiandb.Logger;
import com.ibm.gaiandb.Util;
import com.ibm.gaiandb.diags.GDBMessages;
/**
* This class handles 'call' and 'values' statements respectively for GaianDB procedure and simple function invocations.
* It also delegates queries when appropriate.
*
* @author DavidVyvyan
*/
public class LitePreparedStatement extends VTI60 implements IFastPath, GaianChildVTI {
// Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice.
public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2011";
private static final Logger logger = new Logger( "LitePreparedStatement", 35 );
// Delegate node details, used only if query needs delegating - in which case the associated connection needs closing at the end...
private String delegateNodeConnectionDetails = null;
// Inner class for looking up a java stored procedure of function
private class SPFOperation {
private final Method method;
private final Object[] args;
private String returnDefinition;
private SPFOperation(Method method, Object[] args, String returnDefinition) {
super();
this.method = method;
this.args = args;
this.returnDefinition = returnDefinition;
}
}
private final String opName;
private final String[] args;
private final ResultSetMetaData rsmd;
private SPFOperation operation;
private Object spfResult = null;
// private static final Map<String, SPFOperation> procedureMethods = new HashMap<String, SPFOperation>();
// private static final Map<String, SPFOperation> functionMethods = new HashMap<String, SPFOperation>();
// private static final Pattern operationDeclaration = Pattern.compile(
// "(?i)(?:!)?CREATE[\\s]+(?:PROCEDURE|FUNCTION)[\\s]+..."
// );
// static {
// for all function/procedure declarations, parse operation elements as specified in GAIANDB_API string, then populate Maps
// String s;
// Class c = Class.forName(s);
// c.getMethod(name, parameterTypes);
// }
// public LitePreparedStatement() {
// }
//
// public boolean execute( String sql ) throws SQLException {
// if ( null != spfResult ) return false;
//
// LitePreparedStatement ps = (LitePreparedStatement) getConnection().prepareStatement(sql);
// try {
// return ps.executeAsFastPath();
// } catch (StandardException e) {
// logger.logThreadWarning("Exception in LitePreparedStatement.execute(): " + e);
// throw new SQLException(e);
// }
// }
private static final Set<String> allowedOperations = new HashSet<String>() {
private static final long serialVersionUID = 1L;
{
add("maintainConnection2");
add(DELEGATE_SQL_PROC);
add("listNet");
}
};
public static boolean isAllowedOperation(String opName) {
for (String s : allowedOperations) {
if (s.equalsIgnoreCase(opName)) {
return true;
}
}
return false;
}
private static ResultSetMetaData valuesIntRsmd = null;
// private static ResultSetMetaData valuesStringRsmd = null;
private static ResultSetMetaData listNetRsmd = null;
private static Class<?> stringClass = null;
public LitePreparedStatement( String opName, String[] args ) throws Exception {
this.opName = opName;
this.args = args;
if ( opName.equals("values 1") ) {
spfResult = 1;
rsmd = null == valuesIntRsmd ? valuesIntRsmd = new GaianResultSetMetaData("RC INT") : valuesIntRsmd;
} else if ( opName.equalsIgnoreCase("listNet") ) {
rsmd = null != listNetRsmd ? listNetRsmd : ( listNetRsmd = new GaianResultSetMetaData(
"hostname "+Util.SSTR+",interface "+Util.SSTR+",description "+Util.MSTR+
",ipv4 "+Util.SSTR+",broadcast "+Util.SSTR+",NetPrefixLength INT" ) );
} else if ( opName.equalsIgnoreCase("maintainConnection2") ) {
if ( null == stringClass ) stringClass = Class.forName("java.lang.String");
Class<?> c = Class.forName("com.ibm.gaiandb.GaianDBConfigProcedures");
Method m = c.getMethod("maintainConnection2", new Class[]{ stringClass, stringClass, stringClass, stringClass });
String rdef = "RC VARCHAR(500)";
operation = new SPFOperation(m, args, rdef);
rsmd = new GaianResultSetMetaData(operation.returnDefinition);
} else if ( DELEGATE_SQL_PROC.equals(opName) ) {
operation = null;
if ( 1 != args.length ) {
logger.logThreadWarning(GDBMessages.NETDRIVER_ARGS_NUMBER_INVALID, "Invalid number of arguments "+args.length+
" detected for '"+DELEGATE_SQL_PROC+"(<sql>)' - ignoring call");
rsmd = null;
} else {
ResultSet rs = delegateSqlToNearestCapableNode( args[0] );
rsmd = null == rs ? null : rs.getMetaData();
spfResult = rs;
}
} else {
// need to execute procedures to obtain an rsmd
operation = null;
rsmd = null;
}
}
@Override
public ResultSetMetaData getMetaData() throws SQLException {
if ( isClosed ) throw new SQLException("Cannot complete getMetaData(): LitePreparedStatement is closed");
return rsmd;
}
@Override
public boolean execute() throws SQLException {
return executeAsFastPath();
}
public boolean executeAsFastPath() throws SQLException {
if ( isClosed ) throw new SQLException("Cannot complete executeAsFastPath(): LitePreparedStatement is closed");
if ( null != spfResult ) // query already executed
return true;
try {
if ( opName.equalsIgnoreCase("listNet") )
spfResult = GaianDBUtilityProcedures.getNetInfoForClosestMatchingIP(args[0]);
else
spfResult = operation.method.invoke(null, operation.args);
} catch (Exception e) {
throw new SQLException("Unable to executeAsFastPath(): " + e);
}
// logger.logThreadInfo("executeAsFastPath() successfully invoked method: " + operation.method.getName() + ", result: " + spfResult );
return true;
}
public int nextRow(DataValueDescriptor[] dvdr) throws StandardException, SQLException {
if ( isClosed ) throw new SQLException("Cannot complete nextRow(): LitePreparedStatement is closed");
if ( null == spfResult ) return IFastPath.SCAN_COMPLETED;
if ( spfResult instanceof ResultSet ) {
logger.logThreadInfo("nextRow() handling ResultSet...");
ResultSet rs = (ResultSet) spfResult;
if ( !rs.next() ) {
rs.close();
spfResult = null;
return IFastPath.SCAN_COMPLETED;
}
for ( int i=0; i<dvdr.length; i++) {
DataValueDescriptor dvd = dvdr[i];
dvd.setValueFromResultSet( rs, i+1, rsmd.isNullable(i+1) != ResultSetMetaData.columnNoNulls );
}
} else {
if ( spfResult instanceof Object[] ) {
Object[] result = (Object[]) spfResult;
for ( int i=0; i<result.length; i++ ) {
// logger.logDetail("Getting column " + (i+1) + ", value: " + result[i] + ", type: " + result[i].getClass().getName());
dvdr[i].setObjectForCast(result[i], true, ( null==result[i] ? null : result[i].getClass().getName() ));
}
} else {
dvdr[0].setObjectForCast(spfResult, true, ( null==spfResult ? null : spfResult.getClass().getName() ));
// logger.logThreadDetail("nextRow() got operation value: " + dvdr[0].toString());
}
spfResult = null; // single result is fetched
}
return IFastPath.GOT_ROW;
}
public void currentRow(ResultSet arg0, DataValueDescriptor[] arg1) throws StandardException, SQLException {}
public void rowsDone() throws StandardException, SQLException {}
private boolean isClosed = false;
@Override
public void close() throws SQLException {
// Recycle the connection used for a delegated query - other call/values invoked queries have their connections closed implicitly
if ( null != spfResult && spfResult instanceof ResultSet && null != delegateNodeConnectionDetails )
DataSourcesManager.getSourceHandlesPool( delegateNodeConnectionDetails )
.push( ((ResultSet) spfResult).getStatement().getConnection() );
isClosed = true;
}
public boolean reinitialise() { return false; } // cannot re-execute this GaianChildVTI
@Override
public boolean isClosed() throws SQLException {
return isClosed;
}
static final String DELEGATE_SQL_PROC = "DELEGATESQL"; // Not a Derby registered procedure - this will only work in lite mode
private ResultSet delegateSqlToNearestCapableNode( String sql ) throws Exception {
int dist = DatabaseConnectionsChecker.getDistanceToServerNode();
if ( 1 > dist ) {
logger.logThreadWarning(GDBMessages.NETDRIVER_ERRONEOUS_REQUEST, "Erroneous request for SQL delegation: distance to server node: "
+ dist + " should be greater than 0 - returning null for query");
return null;
}
logger.logInfo("Delegating SQL (distance to capable node: " + dist + "): " + sql);
String nodeID = DatabaseConnectionsChecker.getBestPathToServer();
String gc = GaianDBConfig.getDiscoveredConnectionID(nodeID);
if ( null == nodeID || null == gc ) {
logger.logThreadWarning(GDBMessages.NETDRIVER_SQL_DELEGATE_NODE_ID_NULL, "Unable to delegate SQL: Unknown node path " + nodeID);
return null;
}
Connection c = null;
try {
delegateNodeConnectionDetails = GaianDBConfig.getRDBConnectionDetailsAsString( gc );
c = DataSourcesManager.getPooledJDBCConnection(
delegateNodeConnectionDetails, DataSourcesManager.getSourceHandlesPool( delegateNodeConnectionDetails ) );
if ( 1 < dist ) // Propagate further unless distance is 1
sql = "call " + DELEGATE_SQL_PROC + "('" + Util.escapeSingleQuotes(sql) + "')";
return c.createStatement().executeQuery( sql );
} catch (SQLException e) {
logger.logThreadWarning(GDBMessages.NETDRIVER_SQL_DELEGATE_ERROR_SQL, "Unable to delegate SQL, cause: " + e);
}
return null;
}
public boolean fetchNextRow(DataValueDescriptor[] row) throws Exception {
return IFastPath.GOT_ROW == nextRow(row);
}
public int getRowCount() throws Exception {
// TODO Auto-generated method stub
return 0;
}
public boolean isBeforeFirst() {
// TODO Auto-generated method stub
return false;
}
public boolean isScrollable() {
// TODO Auto-generated method stub
return false;
}
public void setArgs(String[] args) throws Exception {
// TODO Auto-generated method stub
}
public void setExtractConditions(Qualifier[][] qualifiers,
int[] projectedColumns, int[] physicalColumnsMapping)
throws Exception {
// TODO Auto-generated method stub
}
}