/* * (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.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; import org.apache.derby.iapi.error.StandardException; import org.apache.derby.iapi.types.DataValueDescriptor; import org.apache.derby.vti.VTIMetaDataTemplate; import com.ibm.gaiandb.DataSourcesManager.RDBProvider; import com.ibm.gaiandb.diags.GDBMessages; /** * @author DavidVyvyan */ public class GaianResultSetMetaData extends VTIMetaDataTemplate implements Cloneable { // 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( "GaianResultSetMetaData", 35 ); private final int numberOfColumns; private final int numberOfPhysicalColumns; // Number of non-constant and non-hidden columns. These are "real" leaf data columns private int numberOfExposedColumns = 0; // This number may or may not include the provenance columns // Columns beyond the exposed columns - these don't exist here but we pretend that they do and leave them as NULL // if they are queried. This allows a node to return partial data if its table hasn't been synched up with the rest of the network yet. private int numberOfNullColumns = 0; // private final int numberOfHiddenColumns; // The provenance columns // private int numberOfConstantColumns = 0; // Number of Node or Logical Table constant columns, used for routing queries. // private String originalPhysicalColsDef = null; // private String originalSpecialColsDef = null; private final String columnNames[]; private final String columnDescriptions[]; private final int columnWidths[]; private final int columnTypes[]; private final int columnPrecisions[]; private final int columnScales[]; private final String columnTableNames[]; private final String columnSchemaNames[]; private RDBProvider provider = RDBProvider.Derby; // Hashtable of: column name -> column type definition. // These are used to facilitate the lookup of columns and the checking of their types against propagated table defs. private ConcurrentMap<String, int[]> colNameTypeMappings = new ConcurrentHashMap<String, int[]>(); // The list of columns (stored as "<name> <type>") that do not have a definition here but exist in other definitions for this table // around the network. When a propagated definition that contains such a column reaches us, the col name is added to this vector. private final ArrayList<String> nullColDefs = new ArrayList<String>(); // Derby defaults for DECIIMAL/NUMERIC/FLOAT types private static final int DEFAULT_FLOAT_PRECISION = 53; private static final int DEFAULT_DECIMAL_PRECISION = 5; private static final int DEFAULT_DECIMAL_SCALE = 0; private DataValueDescriptor[] rowTemplate = null; // Mapping of colName+jdbcColTypeID -> constant col value (as String) // Used to set local constant values when meta data is constructed from propagated table def private ConcurrentMap<String,String> localConstantsValues = null; // Pattern used to distinguish ordinary identifiers from identifiers needing to be delimited with double quotes // private static final Pattern ordinaryIdentifierPattern = Pattern.compile("[a-zA-Z]\\w*"); private static final Pattern ordinaryIdentifierPatternFoldedToUpperCase = Pattern.compile("[A-Z][A-Z_0-9]*"); public static String wrapDerbyColumnNameForQueryingIfNotAnOrdinaryIdentifier( final String colName ) { return wrapColumnNameForQueryingIfNotAnOrdinaryIdentifier( colName, RDBProvider.Derby ); } public static String wrapColumnNameForQueryingIfNotAnOrdinaryIdentifier( final String colName, final RDBProvider rdbmsProvider ) { if ( RDBProvider.Hive == rdbmsProvider ) return colName; char endDelimiterChar = RDBProvider.MySQL == rdbmsProvider ? '`' : RDBProvider.MSSQLServer == rdbmsProvider ? ']' : '"'; // Wrap delimited identifiers (i.e. non-ordinary ones) in delimiters and escape inner ones. return ordinaryIdentifierPatternFoldedToUpperCase.matcher(colName).matches() ? colName : (RDBProvider.MSSQLServer == rdbmsProvider ? '[' : endDelimiterChar) + Util.escapeCharactersByDoublingThem(colName, endDelimiterChar) + endDelimiterChar; } @Override public int getColumnCount() { return numberOfExposedColumns + numberOfNullColumns; } private String getNullColName(int nullColsIndex) { String ndef = nullColDefs.get(nullColsIndex); return ndef.substring(0, ndef.indexOf(' ')); } // Method used to extract a null column from beyond the range of normally accessible columns.. this position may change // based on whether provenance and/or explain columns are exposed for a given query, so we need to skip more or less of // these cols to get to the null ones. // This is therefore needed because even though the metadata is cloned in GaianTable to add the null columns, // the meta-data in VTIRDBResult has the old value for numExposedColumns. // (this is the correct base level value but is different for every fwded qry's table def that has extra cols) public String getColumnName(int i, int exposedColCount) { try { if (logger.logLevel == Logger.LOG_ALL) logger.logDetail("getColumnName " + i + ", exposedColCount " + exposedColCount); if ( exposedColCount >= i ) return columnNames[i-1]; else return getNullColName(i-1-exposedColCount); } catch (ArrayIndexOutOfBoundsException e) { logger.logException(GDBMessages.RESULT_GET_COL_NAME_EXPOSED_ERROR, "exposedColCount: " + exposedColCount + ", i: " + i, e); throw e; } } @Override public String getColumnName(int i) { try { if (logger.logLevel == Logger.LOG_ALL) logger.logDetail("getColumnName " + i + ", numberOfExposedColumns " + numberOfExposedColumns); if ( numberOfExposedColumns >= i ) return columnNames[i-1]; else return getNullColName(i-1-numberOfExposedColumns); } catch (ArrayIndexOutOfBoundsException e) { logger.logException(GDBMessages.RESULT_GET_COL_NAME_ERROR, "numberOfExposedColumns: " + numberOfExposedColumns + ", i: " + i, e); throw e; } } @Override public int getColumnType(int i) { try { if ( numberOfExposedColumns >= i ) return columnTypes[i-1]; else return ((int[])colNameTypeMappings.get( getNullColName(i-1-numberOfExposedColumns) ))[0]; } catch (ArrayIndexOutOfBoundsException e) { logger.logException(GDBMessages.RESULT_GET_COL_TYPE_ERROR, "numberOfExposedColumns: " + numberOfExposedColumns + ", i: " + i, e); throw e; } } @Override public int getColumnDisplaySize(int i) { try { if(columnWidths == null) return 0x7fffffff; else if ( numberOfExposedColumns >= i ) return columnWidths[i-1]; else return ((int[])colNameTypeMappings.get( getNullColName(i-1-numberOfExposedColumns) ))[1]; } catch (ArrayIndexOutOfBoundsException e) { logger.logException(GDBMessages.RESULT_GET_COL_DISPLAY_SIZE_ERROR, "numberOfExposedColumns: " + numberOfExposedColumns + ", i: " + i, e); throw e; } } @Override public int getPrecision(int i) { try { if ( numberOfExposedColumns >= i ) return columnPrecisions[i-1]; else return ((int[])colNameTypeMappings.get( getNullColName(i-1-numberOfExposedColumns) ))[2]; } catch (ArrayIndexOutOfBoundsException e) { logger.logException(GDBMessages.RESULT_GET_PRECISION_ERROR, "numberOfExposedColumns: " + numberOfExposedColumns + ", i: " + i, e); throw e; } } @Override public int getScale(int i) { try { if ( numberOfExposedColumns >= i ) return columnScales[i-1]; else return ((int[])colNameTypeMappings.get( getNullColName(i-1-numberOfExposedColumns) ))[3]; } catch (ArrayIndexOutOfBoundsException e) { logger.logException(GDBMessages.RESULT_GET_SCALE_ERROR, "numberOfExposedColumns: " + numberOfExposedColumns + ", i: " + i, e); throw e; } } @Override public String getTableName(int i) { return -1 < i && i < columnTableNames.length ? columnTableNames[i-1] : null; } @Override public String getSchemaName(int i) { return -1 < i && i < columnTableNames.length ? columnSchemaNames[i-1] : null; } /** * Return the column index of the specified column name (1-based), or -1 if not found. * * @param columnName * @return */ public int getColumnPosition(String columnName) { for (int i = 0; i < columnNames.length; i++) { if (columnNames[i].equalsIgnoreCase(columnName)) { return i+1; } } return -1; } public String getColumnDescription(int i) { return columnDescriptions[i-1]; } public String getColumnTypeDescriptionGaianDB(int i) { String descr = columnDescriptions[i-1]; return descr.substring( descr.indexOf(' ')+1 ); } public int isNullable(int i) { return ResultSetMetaData.columnNullable; //columnNullableUnknown; } public int getExposedColumnCount() { return numberOfExposedColumns; } public int getPhysicalColumnCount() { return numberOfPhysicalColumns; } // public int getConstantColumnCount() { // return numberOfConstantColumns; // } private String getColumnsDefinition( int numCols, boolean trimConstantValues ) { StringBuffer colDefs = new StringBuffer(); if ( 0<numCols ) colDefs.append( trimConstantValues ? trimConstantValueFromTypeDescription( columnDescriptions[0] ) : columnDescriptions[0] ); for ( int i=1; i<numCols; i++ ) colDefs.append( ", " + ( trimConstantValues ? trimConstantValueFromTypeDescription( columnDescriptions[i] ) : columnDescriptions[i] ) ); return colDefs.toString(); } // The full table definition public String getColumnsDefinition() { return getColumnsDefinition(numberOfColumns, false); } public String getColumnsDefinitionExcludingHiddenOnes() { return getColumnsDefinition(numberOfColumns - GaianDBConfig.NUM_HIDDEN_COLS, false); } public String getColumnsDefinitionExcludingConstantValues() { return getColumnsDefinition(numberOfColumns, true); } // This is the definition we show users from listlts() for example - no need for constant values here public String getColumnsDefinitionExcludingHiddenOnesAndConstantValues() { return getColumnsDefinition(numberOfColumns - GaianDBConfig.NUM_HIDDEN_COLS, true); } // This is the definition given to Derby based on the cols involved in a query public String getColumnsDefinitionForExposedColumns() { StringBuffer colDefs = new StringBuffer(); if ( 0 < numberOfExposedColumns ) colDefs.append(columnDescriptions[0]); for ( int i=1; i<numberOfExposedColumns; i++ ) colDefs.append(", " + columnDescriptions[i]); if ( 0 < numberOfNullColumns ) { int i = 0; if ( 0 == numberOfExposedColumns ) colDefs.append( nullColDefs.get(i++) ); for ( ; i<numberOfNullColumns; i++ ) colDefs.append(", " + nullColDefs.get(i)); } return colDefs.toString(); } // // This is the definition for all columns that are populated - i.e. that also need caching - NO: must cache nulls too :( // public String getColumnsDefinitionForExposedColumnsExceptNulls() { // StringBuffer colDefs = new StringBuffer(); // if ( 0 < numberOfExposedColumns ) colDefs.append(columnDescriptions[0]); // for ( int i=1; i<numberOfExposedColumns; i++ ) colDefs.append(", " + columnDescriptions[i]); // return colDefs.toString(); // } public String getColumnNames() { StringBuffer cols = new StringBuffer(); if ( 0 < numberOfExposedColumns ) cols.append(columnNames[0]); for ( int i=1; i<numberOfExposedColumns; i++ ) cols.append(", " + columnNames[i]); return cols.toString(); } private static String trimConstantValueFromTypeDescription( String td ) { int li = td.lastIndexOf(' '); return td.indexOf(' ') != li ? td.substring(0, li) : td; } // public String getDefinitionForVisibleColumnsIn( HashSet colIDs ) { // // int numberOfVisibleColumns = numberOfColumns - GaianDBConfig.PROVENANCE_COLS.length - GaianDBConfig.EXPLAIN_COLS.length; // // StringBuffer colDefs = new StringBuffer(); // // Iterator i = colIDs.iterator(); // if ( i.hasNext() ) { // int colID = ((Integer) i.next()).intValue(); // 0-based // if ( colID < numberOfVisibleColumns ) // colDefs.append( columnDescriptions[ colID ] ); // } // // while (i.hasNext()) { // int colID = ((Integer) i.next()).intValue(); // 0-based // if ( colID < numberOfVisibleColumns ) // colDefs.append( ", " + columnDescriptions[ colID ] ); // } // // return colDefs.toString(); // } // public GaianResultSetMetaData(int numCols, String colNames[], int colWidths[], int colTypes[]) // { // numberOfColumns = numCols; // columnNames = colNames; // columnWidths = colWidths; // columnTypes = colTypes; // } // public void setRowTemplate( DataValueDescriptor[] row ) { // rowTemplate = row; // } public DataValueDescriptor[] getRowTemplate() { return rowTemplate; } // boolean that tells us if the hidden provenance and explain cols were never actually included in the definition private boolean isTailColumnsInDefinition = true; public void excludeTailColumns( int numCols ) { if ( isTailColumnsInDefinition ) { // Ensure number of exposed cols is never less than 0.. which may be the case if a null md was created... numberOfExposedColumns = Math.max( 0, numberOfColumns - numCols ); } } public void includeNullColumns() { numberOfNullColumns = null != nullColDefs ? nullColDefs.size() : 0; } public void excludeNullColumns() { numberOfNullColumns = 0; } public GaianResultSetMetaData() throws Exception { this( null, null, (String)null ); // have to cast (String so it picks the right constructor and not the one below } public GaianResultSetMetaData( ResultSetMetaData rsmd, String specialColsDef ) throws Exception { this(rsmd, specialColsDef, RDBProvider.Derby); } public GaianResultSetMetaData( ResultSetMetaData rsmd, String specialColsDef, RDBProvider provider ) throws Exception { if ( null == rsmd ) numberOfPhysicalColumns = 0; else numberOfPhysicalColumns = rsmd.getColumnCount(); if ( null == specialColsDef ) specialColsDef = ""; String[] specialCols = GaianDBConfig.getColumnsDefArray( specialColsDef ); // if ( null == specialCols ) specialCols = new String[0]; // String[] hiddenCols = GaianDBConfig.PROVENANCE_COLS; // numberOfHiddenColumns = hiddenCols.length; this.provider = provider; numberOfColumns = numberOfPhysicalColumns + specialCols.length; numberOfExposedColumns = numberOfColumns; // this is set properly later upon GaianTable construction logger.logInfo("Meta data column counts: physical cols: " + numberOfPhysicalColumns + ", special cols: " + specialCols.length); columnNames = new String[ numberOfColumns ]; columnTypes = new int[ numberOfColumns ]; columnWidths = new int[ numberOfColumns ]; columnPrecisions = new int[ numberOfColumns ]; columnScales = new int[ numberOfColumns ]; columnDescriptions = new String[ numberOfColumns ]; columnTableNames = new String[ numberOfColumns ]; columnSchemaNames = new String[ numberOfColumns ]; for ( int i=0; i<numberOfPhysicalColumns; i++ ) { int type = rsmd.getColumnType(i+1); columnTypes[i] = type; columnNames[i] = rsmd.getColumnName(i+1); columnWidths[i] = rsmd.getColumnDisplaySize(i+1); try { columnTableNames[i] = rsmd.getTableName(i+1); } catch( SQLException e ) {} // ignore - could be a FileImportMetaData try { columnSchemaNames[i] = rsmd.getSchemaName(i+1); } catch( SQLException e ) {} // ignore - could be a FileImportMetaData String typeSize = ""; switch ( type ) { case Types.CHAR: case Types.BINARY: case Types.VARCHAR: case Types.VARBINARY: case Types.CLOB: case Types.BLOB: case Types.NCHAR: case Types.NVARCHAR: case Types.NCLOB: // double the sizes for these? (because we downcast them..) typeSize = "(" + columnWidths[i] + ")"; break; case Types.DECIMAL: case Types.NUMERIC: // Precision is the total number of stored digits (before and after the decimal point) // Scale is the number of digits AFTER the decimal point. The number of digits BEFORE is therefore precision-scale // In Oracle, scale can also be negative - which is a left-shift value for which all digits BEFORE the decimal point are 0. // e.g. In Oracle, A NUMERIC(5,-2) column type will have value 12345 inserted as 12300. Derby does not support this. int precision = columnPrecisions[i] = rsmd.getPrecision(i+1); int scale = columnScales[i] = rsmd.getScale(i+1); if ( RDBProvider.Oracle.equals(this.provider) && -127 == scale ) { // FLOAT, REAL or DOUBLE - exposed by Oracle driver as NUMERIC(<binary_precision>, <not_used>) columnTypes[i] = Types.FLOAT; // override the given type (so we also ignore precision and scale) break; // Do not set typeSize - ignore provider size constraints - it is the provider's job to enforce these. } if ( 31 < precision ) columnPrecisions[i] = 31; // truncate to maximum if ( precision < scale || 0 > scale ) { columnScales[i] = DEFAULT_DECIMAL_SCALE; if ( columnPrecisions[i] < columnScales[i] ) columnPrecisions[i] = DEFAULT_DECIMAL_PRECISION; logger.logWarning(GDBMessages.RESULT_PROVIDER_VALUES_INVALID, "Invalid RDBMS values for precision and scale. " + provider + " column: " + columnNames[i] + ' ' + rsmd.getColumnTypeName(i+1) + "("+precision+","+scale+"): Derby requirements are: precision >= scale >= 0;" + "Using values: ("+columnPrecisions[i]+","+columnScales[i]+")"); } typeSize = "(" + columnPrecisions[i] + ", " + columnScales[i] + ")"; break; case Types.FLOAT: columnPrecisions[i] = rsmd.getPrecision(i+1); typeSize = "(" + columnPrecisions[i] + ")"; break; } columnDescriptions[i] = columnNames[i] + " " + lookupColumnTypeNameGaianDB(columnTypes[i], this.provider) + typeSize; // rsmd.getColumnTypeName(i+1) + typeSize; logger.logDetail("Set colDescription["+i+"]: " + columnDescriptions[i]); } setupColumns( new String[numberOfPhysicalColumns], specialCols, numberOfPhysicalColumns ); buildRowTemplate( specialCols ); addQryNullColumns(); logger.logInfo("Retrieved meta data for columns: " + Arrays.asList(columnNames)); } // Used for cases where the result HAD to be computed in order to obtain the ResultSetMetaData - e.g. for procedure calls private ResultSet retainedResultSet = null; // Only valid once, when we need to get that initial meta-data private int retainedUpdateCount = -2; // When set, this is only valid once private String dataSourceWrapperRetainingResult = null; // Parent connection acting as container when running a stored procedure. // This is the one that needs recycling... NOT the 'default' connection from which the resultSet actually comes... private Connection parentCalledProcedureConnection = null; public GaianResultSetMetaData( ResultSet rs, String dsWrapperID, boolean excludeTailColumns, Connection parentCalledProcedureConnection ) throws Exception { this( rs.getMetaData(), excludeTailColumns ? "" : GaianDBConfig.getSpecialColumnsDef(null) ); retainedResultSet = rs; isTailColumnsInDefinition = !excludeTailColumns; this.parentCalledProcedureConnection = parentCalledProcedureConnection; dataSourceWrapperRetainingResult = dsWrapperID; } public GaianResultSetMetaData( final int updateCountToRetain, String dsWrapperID ) throws Exception { this("UPDATE_COUNT INT", GaianDBConfig.getSpecialColumnsDef(null)); retainedUpdateCount = updateCountToRetain; dataSourceWrapperRetainingResult = dsWrapperID; } public ResultSet getRetainedResultSet(String dsWrapperID) { if ( dsWrapperID.equals(dataSourceWrapperRetainingResult) ) { boolean isSet = null != retainedResultSet; logger.logThreadInfo("Looking up retained result set.. isSet? " + isSet); if ( isSet ) { ResultSet rs = retainedResultSet; retainedResultSet = null; // invalidate this once it has been retrieved dataSourceWrapperRetainingResult = null; return rs; } } return null; } public Connection getParentCalledProcedureConnection(String dsWrapperID) { if ( dsWrapperID.equals(dataSourceWrapperRetainingResult) ) { boolean isSet = null != parentCalledProcedureConnection; logger.logThreadInfo("Looking up parentCalledProcedureConnection.. isSet? " + isSet); if ( isSet ) { Connection c = parentCalledProcedureConnection; parentCalledProcedureConnection = null; // invalidate this once it has been retrieved dataSourceWrapperRetainingResult = null; return c; } } return null; } public int getRetainedUpdateCount(String dsWrapperID) { if ( dsWrapperID.equals(dataSourceWrapperRetainingResult) ) { boolean isSet = -2 != retainedUpdateCount; logger.logThreadInfo("Looking up retained update count.. isSet? " + (-2 != retainedUpdateCount)); if ( isSet ) { int rc = retainedUpdateCount; retainedUpdateCount = -2; // invalidate this once it has been retrieved dataSourceWrapperRetainingResult = null; return rc; } } return -2; } public boolean hasRetainedResult() { return null != dataSourceWrapperRetainingResult; //null != retainedResultSet || -2 != retainedUpdateCount; } // /** // * Build the original ResultSetMetaData, including hidden provenance columns by default. // * If the query does not request them, then it will call "excludeProvenanceColumns()" // * // * @param colDefs // * @throws Exception // */ // public GaianResultSetMetaData( String colDefs[] ) throws Exception { // // // Note colDefs include constant columns (and specialCols = constantCols + hiddenCols) // // String[] hiddenCols = GaianDBConfig.PROVENANCE_COLS; // numberOfHiddenColumns = hiddenCols.length; // // numberOfColumns = colDefs.length + numberOfHiddenColumns; // numberOfExposedColumns = numberOfColumns; // // logger.logInfo("Creating columns meta data using colDefs: " + // Arrays.asList(colDefs) + ", hiddenCols: " + Arrays.asList(hiddenCols)); // // logger.logInfo("Column counts: numCols = " + numberOfColumns + // ", physical + constant = " + colDefs.length + ", hidden = " + numberOfHiddenColumns); // // columnNames = new String[ numberOfColumns ]; // columnTypes = new int[ numberOfColumns ]; // columnWidths = new int[ numberOfColumns ]; // columnDescriptions = new String[ numberOfColumns ]; // // setupColumns( colDefs, hiddenCols, 0 ); // } public GaianResultSetMetaData( String physicalColsDef ) throws Exception { this( physicalColsDef, null, null ); } /** * Build the original ResultSetMetaData, including hidden provenance columns by default. * If the query does not request them, then it will call "excludeProvenanceColumns()" * * @param colDefs * @throws Exception */ // public GaianResultSetMetaData( String[] physicalCols, String[] specialCols ) throws Exception { public GaianResultSetMetaData( String physicalColsDef, String specialColsDef ) throws Exception { this( physicalColsDef, specialColsDef, null ); // originalPhysicalColsDef = physicalColsDef; // originalSpecialColsDef = specialColsDef; } // public boolean wasBuiltFrom( String physicalColsDef, String specialColsDef ) { // if ( null == specialColsDef && null == physicalColsDef || // null == specialColsDef ^ null == originalSpecialColsDef || // null == physicalColsDef ^ null == originalPhysicalColsDef ) return false; // // return ( ( null == physicalColsDef || physicalColsDef.intern() == originalPhysicalColsDef.intern() ) && // ( null == specialColsDef || specialColsDef.intern() == originalSpecialColsDef.intern() ) ); // } /** * Build a meta data object based on definitions for physical columns, special columns, and local constant columns. * Physical columns are basic column definitions comprising of a name and type definition (with possible precision value(s)). * Special columns *may* include system columns such as PROVENANCE and EXPLAIN columns. It may also contain constant column * definitions which have a 3rd token to specify the value of the constant. * The local constant column definitions are only passed in when the constant columns defined in the special columns definitions * contain constants for another node or table. These are replaced by the values from the local constant column defs. * * @param physicalColsDef * @param specialColsDef * @param localConstantsDef * @throws Exception */ public GaianResultSetMetaData( String physicalColsDef, String specialColsDef, String localConstantsDef ) throws Exception { if ( null == physicalColsDef ) physicalColsDef = ""; if ( null == specialColsDef ) specialColsDef = ""; String[] physicalCols = GaianDBConfig.getColumnsDefArray( physicalColsDef ); String[] specialCols = GaianDBConfig.getColumnsDefArray( specialColsDef ); if ( null == physicalCols ) physicalCols = new String[0]; if ( null == specialCols ) specialCols = new String[0]; numberOfPhysicalColumns = physicalCols.length; // String[] hiddenCols = GaianDBConfig.PROVENANCE_COLS; // numberOfHiddenColumns = hiddenCols.length; numberOfColumns = numberOfPhysicalColumns + specialCols.length; numberOfExposedColumns = numberOfColumns; // this is set properly later upon GaianTable construction logger.logInfo("Creating columns meta data using physical cols: " + Arrays.asList(physicalCols) + ", special cols: " + Arrays.asList(specialCols)); columnNames = new String[ numberOfColumns ]; columnTypes = new int[ numberOfColumns ]; columnWidths = new int[ numberOfColumns ]; columnPrecisions = new int[ numberOfColumns ]; columnScales = new int[ numberOfColumns ]; columnDescriptions = new String[ numberOfColumns ]; columnTableNames = new String[ numberOfColumns ]; columnSchemaNames = new String[ numberOfColumns ]; setupColumns( physicalCols, specialCols, 0 ); // Now prepare to load constant values for special columns // First check if we have locally defined consant values to replace ones that may have // been passed in specialColsDef - this would happen if we are constructing meta data for a propagated table def. if ( null != localConstantsDef ) { // The constant col values *must* be replaced by local ones. If they don't exist locally they will be null localConstantsValues = new ConcurrentHashMap<String, String>(); String[] localConstantCols = GaianDBConfig.getColumnsDefArray( localConstantsDef ); for ( int i=0; i<localConstantCols.length; i++ ) { String[] elmts = Util.splitByTrimmedDelimiter( localConstantCols[i], ' ' ); String colName = elmts[0].toUpperCase(); String typeName = elmts[1].toUpperCase(); int typeDetails[] = getColumnTypeDetailsFromStringDef( colName, typeName ); int colType = typeDetails[0]; localConstantsValues.put( colName+colType, elmts[2] ); } } // System.out.println("LT col type for col 0: " + getColumnTypeNameGaianDB(1)); // System.out.println("derby col descr for col 0: " + getColumnTypeDescriptionDerby(1)); // System.out.println("gaiandb col descr for col 0: " + getColumnTypeDescriptionGaianDB(1)); // Now create constant col DataValueDescriptor values - // When rows are fetched that match this meta-data, they may sometimes refer to the row template // to fill in constant col values on the fly... (see GaianResult.nextFastPathRow()) buildRowTemplate( specialCols ); addQryNullColumns(); } /** * Process all columns defined by GAIANDB, i.e. that have a textual definition that hasn't yet been loaded. * 'fromColumns' is the number of non-GAIANDB columns, which are already processed and can therefore be skipped. * * @param colDefs * @param extraCols * @param fromColumn * @throws Exception */ private void setupColumns( String[] colDefs, String[] extraCols, int fromColumn ) throws Exception { // A set of the column names - used to prevent duplication - initialise it with all column names that have already been defined. HashSet<String> colNamesSet = new HashSet<String>(); for (int i=0; i<fromColumn; i++) colNamesSet.add( columnNames[i] ); for (int i=fromColumn; i<numberOfColumns; i++) { // Remember this column as it was declared columnDescriptions[i] = ( i<colDefs.length ? colDefs[i] : extraCols[i-colDefs.length] ); String[] nameType = GaianDBConfig.getColumnSplitBySpaces( columnDescriptions[i] ); logger.logDetail("Processing column with info: " + Arrays.asList(nameType)); if ( 2 > nameType.length ) throw new Exception("Unable to parse column " + (i+1) + " defined as: '" + columnDescriptions[i] + "': 2 tokens must be specified to identify the name and type of the column"); String colName = nameType[0]; //.toUpperCase(); String typeName = nameType[1].toUpperCase(); if ( colNamesSet.contains( colName ) ) { // if ( Arrays.asList( GaianDBConfig.HIDDEN_COL_NAMES ).contains(colName) ) { // logger.logInfo("Renaming special column: " + colName + " to " + (colName+2)); // colName += 2; // } else throw new Exception("Duplicate column name defined: " + colName + ". Ensure that the column names do not match one of the hidden provenance columns: " + GaianDBConfig.GDB_NODE + ", " + GaianDBConfig.GDB_LEAF ); } colNamesSet.add( colName ); int typeDetails[] = getColumnTypeDetailsFromStringDef( colName, typeName ); columnNames[i] = colName; columnTypes[i] = typeDetails[0]; columnWidths[i] = typeDetails[1]; columnPrecisions[i] = typeDetails[2]; columnScales[i] = typeDetails[3]; columnTableNames[i] = null; columnSchemaNames[i] = GaianDBConfig.getGaianNodeUser(); // columnDescriptions[i] = colName + " " + typeName as returned in typeDetails + // ( nameType.length > 2 ? nameType[2] : "" ); colNameTypeMappings.put( columnNames[i], typeDetails ); // if ( nameType.length>2 && "DEFAULT".equalsIgnoreCase(nameType[2]) ) // defaultColumnDVDs } } private int[] getColumnTypeDetailsFromStringDef( String colName, String typeName ) throws Exception { int type, width=-1, precision=0, scale=0; // THIS IS THE LIST OF DERBY TYPES SUPPORTED IN GAIANDB LOGICAL TABLE DEFINITIONS - // WE DO NOT ATTEMPT TO TRANSLATE OTHER LOGICAL TABLE COLUMN TYPES INTO DERBY TYPES (FOR NOW) // NOTE: THESE DERBY TYPE DEFS ARE NOT SUPPORTED - INSTEAD WE USE THE ALTERNATIVES LISTED AGAINST THEM: // LONG VARCHAR -> LONGVARCHAR // { CHAR | CHARACTER } FOR BIT DATA -> BINARY // { VARCHAR | CHAR VARYING | CHARACTER VARYING } FOR BIT DATA -> VARBINARY // LONG VARCHAR FOR BIT DATA -> LONGVARBINARY // NOTE THAT THE FOLLOWING TYPES *ARE* SUPPORTED EVEN THOUGH THEY DO NOT APPEAR EXPLICITELY BELOW // DOUBLE PRECISION (== DOUBLE) // CHARACTER (== CHAR) // INTEGER (==INT) // DECIMAL (==DEC) // EXTRA TYPES: // BIT // BOOLEAN // TINYINT // NULL if ( typeName.startsWith("CHAR") ) type = Types.CHAR; else if ( typeName.startsWith("VARCHAR") ) type = Types.VARCHAR; else if ( typeName.startsWith("DEC") ) type = Types.DECIMAL; else if ( typeName.startsWith("NUMERIC") ) type = Types.NUMERIC; else if ( typeName.startsWith("FLOAT") ) type = Types.FLOAT; else if ( typeName.startsWith("BINARY") ) type = Types.BINARY; else if ( typeName.startsWith("VARBINARY") ) type = Types.VARBINARY; else if ( "LONGVARCHAR".equals( typeName ) ) type = Types.LONGVARCHAR; else if ( "LONGVARBINARY".equals( typeName ) ) type = Types.LONGVARBINARY; else if ( "BIT".equals( typeName ) ) type = Types.BIT; else if ( "BOOLEAN".equals( typeName ) ) type = Types.BOOLEAN; else if ( typeName.startsWith("BLOB") ) type = Types.BLOB; else if ( typeName.startsWith("CLOB") ) type = Types.CLOB; else if ( "DATE".equals( typeName ) ) type = Types.DATE; else if ( "TIME".equals( typeName ) ) type = Types.TIME; else if ( "TIMESTAMP".equals( typeName ) ) type = Types.TIMESTAMP; else if ( typeName.startsWith("INT") ) type = Types.INTEGER; else if ( "BIGINT".equals( typeName ) ) type = Types.BIGINT; else if ( "SMALLINT".equals( typeName ) ) type = Types.SMALLINT; else if ( "TINYINT".equals( typeName ) ) type = Types.TINYINT; else if ( "DOUBLE".equals( typeName ) ) type = Types.DOUBLE; else if ( "REAL".equals( typeName ) ) type = Types.REAL; else if ( "NULL".equals( typeName ) ) type = Types.NULL; else throw new Exception("Invalid type definition syntax for column '" + colName + "': " + typeName); String lendef[] = typeName.split("[(|)|,]"); try { switch ( type ) { case Types.CHAR: case Types.NCHAR: case Types.BINARY: width = 2 > lendef.length ? 1 : Integer.parseInt( lendef[1] ); break; case Types.BLOB: case Types.CLOB: case Types.NCLOB: width = 2 * 1024^3; // default size case Types.VARCHAR: case Types.NVARCHAR: case Types.VARBINARY: if ( 2 > lendef.length ) { if ( -1 < width ) break; // use default BLOB or CLOB width - no exception. String w = "Missing type length definition for column '" + colName + "': " + typeName; // logger.logWarning(GDBMessages.RESULT_COLUMN_LENGTH_DEF_MISSING, w); // doesn't help to log this here as we don't know what implications are. throw new SQLException(w); } String wdef = lendef[1]; int multiplier = 1; if ( Types.BLOB == type || Types.CLOB == type ) { // look to see if we have a K, M or G marker int lastCharIdx = wdef.length()-1; char lastChar = wdef.charAt(lastCharIdx); if ( 'k' == lastChar || 'K' == lastChar ) multiplier = 1024; else if ( 'm' == lastChar || 'M' == lastChar ) multiplier = 1024^2; else if ( 'g' == lastChar || 'G' == lastChar ) multiplier = 1024^3; if ( 1 < multiplier ) wdef = wdef.substring(0, lastCharIdx); // adjust wdef if there was a marker } width = Integer.parseInt( wdef ) * multiplier; // note this includes end of string delimiter, so max chars is 1 less. // TODO: Check that width does not extend outside of allowed [min, max] range... - also implement truncation in GaianResult.nextFastPathRow() break; case Types.LONGVARCHAR: case Types.LONGVARBINARY: width = 32702; break; // Max size for Derby LONG VARCHAR + 2 padding case Types.DECIMAL: case Types.NUMERIC: precision = 2 > lendef.length ? DEFAULT_DECIMAL_PRECISION : Integer.parseInt( lendef[1] ); scale = 3 > lendef.length ? DEFAULT_DECIMAL_SCALE : Integer.parseInt( lendef[2] ); width = precision + 3; break; // Add the "." and 2 for padding. case Types.FLOAT: precision = 2 > lendef.length ? DEFAULT_FLOAT_PRECISION : Integer.parseInt( lendef[1] ); width = precision + 2; break; // 2 for padding. case Types.BIT: case Types.BOOLEAN: width = 7; break; // 5 + 2 padding case Types.DATE: width = 12; break; // 1965-01-01 -> 10 + 2 padding case Types.TIME: width = 12; break; // 23:53:25.0 -> 10 + 2 padding case Types.TIMESTAMP: width = 23; break; // 2006-09-23 23:53:25.0 -> 21 + 2 padding case Types.INTEGER: width = 12; break; // 10 + 2 padding case Types.BIGINT: width = 22; break; // 20 + 2 padding case Types.SMALLINT: width = 7; break; // 5 + 2 padding case Types.TINYINT: width = 5; break; // 3 + 2 padding case Types.DOUBLE: width = 22; break; // 22 ??? case Types.REAL: width = 22; break; // 22 ??? case Types.NULL: width = 6; break; // 4 + 2 padding // case Types.ARRAY: width = 22; break; // 22 ??? // case Types.JAVA_OBJECT: case Types.STRUCT: width = 22; break; // 22 ??? // case Types.REF: case Types.BLOB: case Types.CLOB: case Types.ARRAY: width = 22; break; // 22 ??? // case Types.DATALINK: width = 22; break; // 22 ??? // case Types.REF: width = 22; break; // 22 ??? // case Types.DISTINCT: case Types.OTHER: width = 22; break; // 22 ??? // No distinct type supported default: String w = "Unsupported JDBC type: " + type; logger.logWarning(GDBMessages.RESULT_JDBC_TYPE_UNSUPPORTED, w); throw new SQLException(w); } } catch ( NumberFormatException e ) { String w = "Invalid length in column specification for column '" + colName + "': " + typeName + ": " + e; logger.logWarning(GDBMessages.RESULT_COLUMN_LENGTH_INVALID, w); throw new SQLException(w); } return new int[] { type, width, precision, scale }; } public String getColumnTypeNameGaianDB( int i ) throws SQLException { return lookupColumnTypeNameGaianDB( getColumnType(i) ); } private static String lookupColumnTypeNameGaianDB( int type ) throws SQLException { return lookupColumnTypeNameGaianDB(type, RDBProvider.Derby); } private static String lookupColumnTypeNameGaianDB( final int type, RDBProvider provider ) throws SQLException { logger.logDetail("lookupColumnTypeNameGaianDB(): jdbcType " + type + " RDBMS Provider " + provider); switch ( type ) { case Types.CHAR: return "CHAR"; case Types.VARCHAR: return "VARCHAR"; case Types.LONGVARCHAR: return "VARCHAR(32672)"; // Converted because comparisons on LVC fail with Derby. case Types.LONGVARBINARY: return "LONGVARBINARY"; case Types.DECIMAL: return "DECIMAL"; case Types.NUMERIC: return "NUMERIC"; case Types.FLOAT: return "FLOAT"; case Types.BINARY: return "BINARY"; case Types.VARBINARY: return "VARBINARY"; case Types.BIT: return "BIT"; case Types.BOOLEAN: return "BOOLEAN"; case Types.BLOB: return "BLOB"; case Types.CLOB: return "CLOB"; case Types.DATE: return "DATE"; case Types.TIME: return "TIME"; case Types.TIMESTAMP: return "TIMESTAMP"; case Types.INTEGER: return "INTEGER"; case Types.BIGINT: return "BIGINT"; case Types.SMALLINT: return "SMALLINT"; case Types.TINYINT: return "TINYINT"; case Types.DOUBLE: return "DOUBLE"; case Types.REAL: return "REAL"; case Types.NULL: return "NULL"; case Types.NCHAR: return "CHAR"; // With Derby 10.8: Feature not implemented: NATIONAL CHAR case Types.NCLOB: return "CLOB"; // With Derby 10.8: Feature not implemented: NCLOB case Types.NVARCHAR: return "VARCHAR"; // With Derby 10.8: Feature not implemented: NATIONAL CHAR VARYING // case Types.ARRAY: // case Types.JAVA_OBJECT: // case Types.STRUCT: // case Types.REF: // case Types.DATALINK: // case Types.DISTINCT: // case Types.OTHER: default: String typeName = "None"; switch ( provider ) { case DB2: switch ( type ) { case Types.OTHER: return "DECIMAL(31,0)"; // DB2 uses Types.OTHER for DECFLOAT } break; case Oracle: // See: http://ss64.com/ora/syntax-datatypes.html // See: http://docs.oracle.com/cd/E11882_01/appdev.112/e13995/constant-values.html switch ( type ) { case 101: return "DOUBLE"; // BINARY_DOUBLE case 100: return "FLOAT"; // BINARY_FLOAT // we convert those ones case -100: // TIMESTAMPNS - deprecated to TIMESTAMP from V9.2.0 case -102: // TIMESTAMP WITH LOCAL TIME ZONE case -101: return "TIMESTAMP"; // TIMESTAMP WITH TIME ZONE - normalised using: <TSTZ_COL> AT LOCAL case -103: return "INTEGER"; // INTERVAL YEAR TO MONTH - normalised to number of months case -104: return "DOUBLE"; // INTERVAL DAY TO SECOND - normalised to seconds with floating point value case Types.ROWID: case Types.OTHER: return "VARCHAR(4000)"; // Oracle uses Types.OTHER for UROWID // we don't support the following case -10: typeName = "CURSOR"; break; } break; case MSSQLServer: switch ( type ) { case -16: return "VARCHAR(32672)"; // XML } break; case Derby: case MySQL: case Other: default: break; } String w = "Unsupported JDBC type from "+provider+": " + type + "\nKnown mapping(s): " + typeName + ".\nPlease review the GaianDB documentation for the list of native RDBMS Unsupported Types."; logger.logWarning(GDBMessages.RESULT_LOOKUP_JDBC_TYPE_UNSUPPORTED, w); throw new SQLException(w); } } // not used public static String lookupColumnTypeNameDerby( int type ) throws SQLException { return morphTypeNameGaian2Derby(lookupColumnTypeNameGaianDB(type), type); } private static String morphTypeNameGaian2Derby( String gdbd, int type ) { switch ( type ) { case Types.BINARY: int idx = gdbd.indexOf('('); return "CHAR" + (-1==idx?"":gdbd.substring(idx)) + " FOR BIT DATA"; // VARBINARY must have a length definition case Types.VARBINARY: return "VARCHAR" + gdbd.substring( gdbd.indexOf('(') ) + " FOR BIT DATA"; case Types.LONGVARBINARY: return "LONG VARCHAR FOR BIT DATA"; case Types.LONGVARCHAR: return "LONG VARCHAR"; default: return gdbd; } } public String getColumnTypeDescriptionDerby( int colIdx ) { return morphTypeNameGaian2Derby(getColumnTypeDescriptionGaianDB(colIdx), getColumnType(colIdx)); } // This method is only used for building a definition for a physical CACHE table to be created in Derby. // For this purpose, this GaianResultSetMetaData should have been created without any hidden, constant or null columns. public String getColumnsDefinitionMorphed2DerbySyntax() { StringBuilder colDefs = new StringBuilder(); if ( 0<numberOfColumns ) colDefs.append( morphColumnDescription2Derby( 1 ) ); for ( int colIdx=2; colIdx < numberOfColumns+1; colIdx++ ) colDefs.append( ", " + morphColumnDescription2Derby( colIdx ) ); return colDefs.toString(); } private String morphColumnDescription2Derby( int colIdx ) { return wrapDerbyColumnNameForQueryingIfNotAnOrdinaryIdentifier( getColumnName(colIdx) ) + ' ' + getColumnTypeDescriptionDerby(colIdx); } /** * Set constant values for the row's special columns that will be set. * These may include: GAIAN_NODE, GAIAN_LEAF, or any constant value set in the properties file. * Any of these may be used for routing queries through the network efficiently. * * The value for GAIAN_LEAF is not known at this stage, as it depends on the propeties of each * child-node as it is being processed. It will be a SQLVarchar with an initial value of null. * * @return * @throws Exception */ private void buildRowTemplate( String[] specialCols ) throws Exception { // numberOfPhysicalColumns = numberOfColumns - specialCols.length; // numberOfConstantColumns = numberOfColumns - numberOfPhysicalColumns - numberOfHiddenColumns; int ltColCount = numberOfColumns; int specialColCount = specialCols.length; logger.logInfo("Building row template from special columns: " + Arrays.asList(specialCols) ); logger.logInfo("ltColCount = " + ltColCount + ", specialColCount (constant + hidden cols) = " + specialColCount); rowTemplate = new DataValueDescriptor[ ltColCount ]; // each DVD is initialised to null for ( int i=0; i<ltColCount; i++ ) if ( null == ( rowTemplate[i] = RowsFilter.constructDVDMatchingJDBCType( columnTypes[i], this.provider ) ) ) throw new Exception("Type not supported for column: " + columnDescriptions[i]); for ( int i=0; i<specialColCount; i++ ) { String colDef = specialCols[i]; String[] colElmts = GaianDBConfig.getColumnSplitBySpaces( colDef ); int ltColIndex = ltColCount - specialColCount + i; // 0-based int colType = columnTypes[ ltColIndex ]; // if ( null == ( rowTemplate[ltColIndex] = RowsFilter.constructDVDMatchingJDBCType( colType ) ) ) // throw new Exception("Type not supported for CONSTANT column: " + columnDescriptions[ltColIndex]); String colName = colElmts[0]; logger.logDetail("Setting DVD for special col: " + colName); if ( GaianDBConfig.GDB_NODE.equalsIgnoreCase( colName ) ) { rowTemplate[ltColIndex].setValue( GaianDBConfig.getGaianNodeID() ); } else if ( ! colName.startsWith( GaianDBConfig.GDB_PREFIX ) ) { // Must be a constant column - expecting constant value, so 3 elements to the column definition if ( 3 > colElmts.length ) throw new Exception("Missing value in definition of CONSTANT column: " + columnDescriptions[ltColIndex]); String colValue = null != localConstantsValues ? (String) localConstantsValues.get( colName + colType ) : colDef.trim().substring(colName.length()).trim().substring(colElmts[1].length()).trim(); if ( null == localConstantsValues && '"' == colValue.charAt(0) ) { // This is a double quoted value, so it MUST be delimited by double quotes and we must remove these and inner escape chars too... if ( 2 > colValue.length() || '"' != colValue.charAt( colValue.length() - 1 ) ) throw new Exception("Unable to build RSMD definition: Double quoted value in CONSTANT column definition does not end with a quote: " + columnDescriptions[ltColIndex]); colValue = colValue.substring(0, colValue.length()-1); for ( int k=1; k<colValue.length(); k++ ) if ( '"' == colValue.charAt(k) && '\\' != colValue.charAt(k-1) ) throw new Exception("Unable to build RSMD definition: Non-escaped nested quote in CONSTANT column definition (backslash required): " + columnDescriptions[ltColIndex]); colValue = Util.stripEscapeCharacterDownOneNestingLevel( colValue.substring(1) , '\\' ); } // colValue might legitimately be null if we are constructing meta data for a propagated table def and // the constant col doesnt exist locally if ( null != colValue ) rowTemplate[ltColIndex].setValue( colValue ); } } logger.logInfo("Built Logical Table DVD template: " + Arrays.asList(rowTemplate)); } // public void addMissingColumnsAsNulls( String referenceTableDef ) throws Exception { // // String[] refColDefs = GaianDBConfig.getColumnDefArray( referenceTableDef ); // for (int i=0; i<refColDefs.length; i++) { // // String[] nameType = GaianDBConfig.getColumnSplitBySpaces( refColDefs[i] ); // String colName = nameType[0]; // if ( colNameTypeMappings.containsKey( colName ) ) // continue; // // // The column from the propagated table def is not defined here - add it to NULL columns // String stype = nameType[1]; // // int typeDetails[] = getColumnTypeDetailsFromStringDef( colName, stype ); // // // Remember this column as a NULL column. // nullColNames.add( colName ); // colNameTypeMappings.put( colName, typeDetails ); // } // } /** * Matching up meta data with a table definition consists in: * - Checking that type definitions that exist in both are identical. * - Creating NULL columns of the correct type in the meta data for columns that exist in the table definition but not in the meta data. */ public boolean matchupWithTableDefinition( String referenceTableDef, boolean fillOutWithNulls ) { //throws Exception { String[] refColDefs = GaianDBConfig.getColumnsDefArray( referenceTableDef ); for (int i=0; i<refColDefs.length; i++) { String refColDef = refColDefs[i]; logger.logInfo("Matching up column: " + refColDef); String[] nameType = GaianDBConfig.getColumnSplitBySpaces( refColDef ); // Note nameType may be a constant col too, in which case the constant value will be in nameType[2], // but we don't need to compare this, as it is just the constant value for the node that propagated the query to us. String colName = nameType[0]; String stype = nameType[1]; int tdRef[]; try { tdRef = getColumnTypeDetailsFromStringDef( colName, stype ); } catch (Exception e) { logger.logException(GDBMessages.RESULT_MATCHUP_ERROR, "Could not match up column defs: ", e); return false; } int[] tdLoc = null; // Establish if we've seen this column as a missing one before. // Note that the type may be different if it has changed or if the query is coming from a different node - so we ONLY MATCH THE COLUMN NAME. boolean wasNotAlreadyANullColumn = true; //!nullColDefs.contains(refColDef); for ( String nullColDef : nullColDefs ) if ( nullColDef.toUpperCase().startsWith(colName.toUpperCase() + ' ' )) { wasNotAlreadyANullColumn = false; break; } if ( null != ( tdLoc = (int[]) colNameTypeMappings.get( colName ) ) && wasNotAlreadyANullColumn ) { // Check type definition is identical - compare type, size, precision and scale values if ( tdLoc[0] != tdRef[0] || tdLoc[1] != tdRef[1] || tdLoc[2] != tdRef[2] || tdLoc[3] != tdRef[3] ) return false; // This column matches - check the next one continue; } if ( fillOutWithNulls ) { logger.logInfo("Adding NULL column to meta data: " + colName); // Check if the column already exists as a NULL column - if not add it if ( wasNotAlreadyANullColumn ) nullColDefs.add( refColDef ); // Set the column type info colNameTypeMappings.put( colName, tdRef ); } } logger.logInfo("Matched up all columns"); // Do not make Null cols visible - this is controlled form the outside - default behaviour is for them to be hidden // numberOfNullColumns = nullColNames.size(); return true; } private void addQryNullColumns() throws Exception { String[] qryCols = GaianDBConfig.getColumnsDefArray( GaianDBConfig.QRYID_COLDEFS + ", " + GaianDBConfig.GDB_CREDENTIALS_COLDEF ); for (int i=0; i<qryCols.length; i++) { String colDef = qryCols[i]; String[] nameType = GaianDBConfig.getColumnSplitBySpaces( colDef ); String colName = nameType[0]; String stype = nameType[1]; int tdRef[] = getColumnTypeDetailsFromStringDef( colName, stype ); nullColDefs.add( colDef ); // Set the column type info colNameTypeMappings.put( colName, tdRef ); } // Do not include these columns by default - they only need including temporarily for Derby's benefit... // numberOfNullColumns = nullColNames.size(); } // This method doesn't really work because the numberOfExposedColumns is only changed in a cloned copy of this meta data. // GAIANDB does not (currently) modify the original copies of the meta data objects which are held by the VTIWrapper objects... // // public boolean isExplain() { // return numberOfColumns == numberOfExposedColumns; // } public void setExplainTemplateColumns( String from, String to, int depth, int precedence ) throws StandardException { int offset = getExplainColumnsOffset(); rowTemplate[ offset ].setValue( from ); rowTemplate[ offset+1 ].setValue( to ); rowTemplate[ offset+2 ].setValue( depth ); rowTemplate[ offset+3 ].setValue( precedence ); // Initial explain count is unknown but cannot be left to be null as it gets confused with the other null constant col which is gaian leaf... // rowTemplate[ offset+4 ].setValue( -1 ); // Don't log anything here as this method is called by GaianTable (and we don't know if this is a System LOG_EXCLUDE query which we don't want to log) } public int getExplainColumnsOffset() { return numberOfColumns - GaianDBConfig.EXPLAIN_COLS.length; } /** * Returns mapping of all column indices in the given rsmd -> col indices of physical cols in this GaianResultSetMetaData * Columns are matched by name. We assume they all exist and we ignore types. * All column indices in the returned array are relative to 0. */ public int[] derivePhysicalColumnsMapping( GaianResultSetMetaData fromRSMD ) throws SQLException { int colCount = fromRSMD.getPhysicalColumnCount(); //getColumnCount(); int[] mapping = new int[colCount]; int j; for ( int i=0; i<colCount; i++ ) { String col = fromRSMD.getColumnName(i+1).toUpperCase(); j=0; for ( ; j<numberOfPhysicalColumns; j++ ) if ( col.equals(getColumnName(j+1)) ) { mapping[i] = j; break; } if ( j == numberOfPhysicalColumns ) mapping[i] = -1; // no mapping for this column } return mapping; } public Object clone() { try { // No need to deep clone the Hashtable as it is not used by cloned instances return super.clone(); } catch (CloneNotSupportedException e) { logger.logException( GDBMessages.RESULT_CLONE_ERROR, "Error: Unexpected Exception caught whilst cloning meta data: ", e ); } return null; } // public String getColumnNames( int exposedColsCount ) { // StringBuffer cols = new StringBuffer(); // if ( 0 < numberOfExposedColumns ) cols.append(columnNames[0]); // for ( int i=1; i<numberOfExposedColumns; i++ ) cols.append(", " + columnNames[i]); // return cols.toString(); // } public String getPhysicalOrConstantColumnNames() { StringBuffer cols = new StringBuffer(); for ( int i=0; i < numberOfColumns - GaianDBConfig.NUM_HIDDEN_COLS ; i++ ) cols.append((0==i?"":", ") + ( wrapDerbyColumnNameForQueryingIfNotAnOrdinaryIdentifier(columnNames[i]) ) ); return cols.toString(); } public String getColumnNamesWrappedIfNotOrdinary() { StringBuffer cols = new StringBuffer(); if ( 0 < numberOfExposedColumns ) cols.append( wrapDerbyColumnNameForQueryingIfNotAnOrdinaryIdentifier(columnNames[0]) ); for ( int i=1; i<numberOfExposedColumns; i++ ) cols.append(", " + wrapDerbyColumnNameForQueryingIfNotAnOrdinaryIdentifier(columnNames[i])); return cols.toString(); } public String getColumnNamesIncludingNullOnes( final int exposedColsCount, final boolean doubleQuoteNonOrdinaryIdentifiers ) { final boolean dq = doubleQuoteNonOrdinaryIdentifiers; StringBuffer cols = new StringBuffer(); for ( int i=0; i<exposedColsCount; i++ ) cols.append((0==i?"":", ") + ( dq ? wrapDerbyColumnNameForQueryingIfNotAnOrdinaryIdentifier(columnNames[i]) : columnNames[i] ) ); for ( int i=0; i<nullColDefs.size(); i++ ) cols.append((0==exposedColsCount && 0==i?"":", ") + ( dq ? wrapDerbyColumnNameForQueryingIfNotAnOrdinaryIdentifier(getNullColName(i)) : getNullColName(i) ) ); return cols.toString(); } // Should only be used for logging... public String toString() { return '[' + getColumnNamesIncludingNullOnes(numberOfExposedColumns, false) + ']'; } public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } public <T> T unwrap(Class<T> iface) throws SQLException { return null; } public boolean isAutoIncrement(int column) throws SQLException { return false; } // Is the column automatically numbered, and thus read-only? public boolean isCaseSensitive(int column) throws SQLException { return false; } // Does a column's case matter? public boolean isSearchable(int column) throws SQLException{ return true; } // Can the column be used in a WHERE clause? }