/*
* (C) Copyright IBM Corp. 2014
*
* LICENSE: Eclipse Public License v1.0
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.ibm.gaiandb.vti;
import com.ibm.db2j.AbstractVTI;
import com.ibm.gaiandb.GaianResultSetMetaData;
import com.ibm.gaiandb.Logger;
import com.ibm.gaiandb.RowsFilter;
import com.ibm.gaiandb.Util;
import java.sql.SQLException;
import java.util.Arrays;
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.iapi.types.SQLChar;
import org.apache.derby.vti.IFastPath;
import org.apache.derby.vti.VTIEnvironment;
/**
* The purpose of this sample class is to help understand the structure of a VTI and when each method is called in the query life cycle.
* Each method logs a message to gaiandb.log when it is invoked by Gaian, and each message is prefixed with the timestamp and class name.
* The messages will appear in gaiandb.log as long as you set LOGLEVEL appropriately, e.g. one of the following:
* call setloglevel('MORE')
* call setloglevel('ALL')
*
* The VTI just returns 1 record with 1 column value holding a random greeting message
*
* @author DavidVyvyan
*/
public class SampleHelloWorldVTI extends AbstractVTI {
// Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice.
public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2014";
// This is Gaian's Logger class - Use an instance of this to merge the VTI's logs with Gaian's.
// The 2nd argument is cosmetic and determines the indentation for its messages in the log file.
// Indentation is usually made larger for deeper levels of Java stack nesting.
private static final Logger logger = new Logger( "SampleHelloWorldVTI", 30 );
private long rowCount = 0;
private Qualifier[][] qualifiers = null; // Conjunctive-normal-form structure representing "where-clause" conditions on columns - See Derby docs
private int[] projectedColumns = null; // Array of column indexes (1-based) selected in the query from this VTI - See Derby doc
private String message;
private static final String[] words1 = new String[] { "Hello", "Greetings", "Welcome", "Hi", "Hey" };
private static final String[] words2 = new String[] { "World", "Earth", "People", "Moon", "Sky" };
private String vtiFirstArgumentFromGaianConfigFile = null;
/**
* The args passed in here are the values set against the '_ARGS' property (in gaiandb_config.properties) of the wrapping data source
*/
public SampleHelloWorldVTI(String vtiArgs) throws Exception {
super(vtiArgs, "SamplePluralizedVTI");
logger.logThreadInfo("Exiting SampleHelloWorldVTI() constructor.");
// Use AbstractVTI.getPrefix() to get the first argument -
vtiFirstArgumentFromGaianConfigFile = getPrefix();
// Note: The 'prefix' argument is also used to resolve static VTI properties relative to the Class name in gaiandb_config.properties
// e.g: SampleHelloWorldVTI.prefixArg.staticPropertyURL=<URL>
// The <URL> property above would be retrieved using:
// String url = AbtsractVTI.getProperty('staticPropertyURL')
}
/**
* Method getMetaData() gives the VTI's table schema, i.e. number of columns, their types, names, sizes etc.
* It may sometimes be appropriate to deduce this from the first row of data..
* This method is always called by the querying engine (Gaian or Derby) *before* query execution.
* If this method is not implemented, Gaian will try to resolve the schema based off a 'schema' property for this VTI in
* gaiandb_config.properties, or failing that it will resolve it based on the schema of the logical table.
*/
@Override public GaianResultSetMetaData getMetaData() throws SQLException {
logger.logThreadInfo("Entered getMetaData()");
// return super.getMetaData();
// Alternate method for enforcing a schema in the VTI - Use GaianResultSetMetaData:
try { return new GaianResultSetMetaData("MSG VARCHAR(20)"); }
catch (Exception e) { throw new SQLException("Unable to build basic table structure for SampleHelloWorldVTI", e); }
}
/**
* The args[] passed here contain all sorts of query context fields - Take a look in gaiandb.log to see them all
*/
@Override public void setArgs(String[] args) throws Exception {
logger.logThreadInfo("Entered setArgs(), args: " + Arrays.asList(args) );
};
/**
* This is called by Gaian to specify the list of columns that are queried
*/
@Override public boolean pushProjection(VTIEnvironment arg0, int[] projectedColumns) throws SQLException {
logger.logThreadInfo("Entered pushProjection(), projectedColumns: " + Util.intArrayAsString(projectedColumns));
this.projectedColumns = projectedColumns;
return true;
}
/**
* IQualifyable interface - used to process predicates against our columns -
*/
@Override public void setQualifiers(VTIEnvironment vtie, Qualifier[][] qualifiers) throws SQLException {
logger.logThreadInfo("Entered setQualifiers(), qualifiers: " + RowsFilter.reconstructSQLWhereClause(qualifiers));
this.qualifiers = qualifiers;
}
/**
* This method is called after the ones above and computes the result.
* Must always return true - this tells Derby to use the IFastPath interface method nextRow() to extract records, rather
* than expecting it to call the VTI's PreparedStatement.executeQuery() method to execute as a regular ResultSet VTI.
*/
@Override public boolean executeAsFastPath() throws StandardException, SQLException {
logger.logThreadInfo("Entered executeAsFastPath()");
if ( null != projectedColumns && 1 != projectedColumns[0] ) {
logger.logInfo("projectedColumns do not include the MESSAGE column - aborting query");
return true;
}
int i1 = (int) (System.nanoTime()/1000 % words1.length);
try { Thread.sleep(10); } catch (Exception e) {}
int i2 = (int) (System.nanoTime()/1000 % words2.length);
String middleText = null == vtiFirstArgumentFromGaianConfigFile ? " " : ' ' + vtiFirstArgumentFromGaianConfigFile + ' ';
message = words1[i1] + middleText + words2[i2] + '!';
return true;
}
/**
* Gaian extracts rows by calling this method repeatedly
*/
@Override public int nextRow(DataValueDescriptor[] row) throws StandardException, SQLException {
logger.logDetail("Entered nextRow(), row.length: " + row.length );
if ( 0 < rowCount++ ) return IFastPath.SCAN_COMPLETED;
row[0] = new SQLChar( message );
if ( true == RowsFilter.testQualifiers( row, qualifiers ) ) return IFastPath.GOT_ROW;
else return IFastPath.SCAN_COMPLETED;
}
/**
* Clears the query objects and returns true
*/
@Override public boolean reinitialise() {
logger.logThreadInfo("Entered reinitialise()");
rowCount = 0;
return true;
}
@Override public int getRowCount() throws Exception { return (int) rowCount; }
/**
* Use this method to empty local heap resources for this instance and set them to null if possible
* This method is called when this VTI instance is discarded (e.g. if it stops being referenced as a data source wrapper)
*/
@Override public void close() throws SQLException { super.close(); }
@Override public boolean isBeforeFirst() throws SQLException { return 0 == rowCount; }
/**
* VTICosting methods - used for JOIN optimisations
*/
@Override public double getEstimatedCostPerInstantiation(VTIEnvironment arg0) throws SQLException { return 0; }
@Override public double getEstimatedRowCount(VTIEnvironment arg0) throws SQLException { return 0; }
@Override public boolean supportsMultipleInstantiations(VTIEnvironment arg0) throws SQLException { return false; }
}