/* * Copyright (C) 2000 - 2010 TagServlet Ltd * * This file is part of Open BlueDragon (OpenBD) CFML Server Engine. * * OpenBD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * Free Software Foundation,version 3. * * OpenBD is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenBD. If not, see http://www.gnu.org/licenses/ * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with any of the JARS listed in the README.txt (or a modified version of * (that library), containing parts covered by the terms of that JAR, the * licensors of this Program grant you additional permission to convey the * resulting work. * README.txt @ http://www.openbluedragon.org/license/README.txt * * http://www.openbluedragon.org/ */ package com.naryx.tagfusion.cfm.engine; import java.io.Serializable; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Types; import java.util.Map; import com.nary.util.FastMap; public class cfQueryResultSetMetaData implements ResultSetMetaData, Serializable { private static final long serialVersionUID = 1L; private ColumnMetaData[] columnList; private boolean isColumnTypeSet; // map column name to column index for fast lookups; index is 1-based private Map<String, Integer> columnNameCache = new FastMap<String, Integer>(FastMap.CASE_INSENSITIVE); public cfQueryResultSetMetaData(ResultSetMetaData rsmd) { isColumnTypeSet = true; try { int numCols = rsmd.getColumnCount(); columnList = new ColumnMetaData[numCols]; for (int i = 0; i < numCols; i++) { ColumnMetaData cmd = new ColumnMetaData(); cmd.autoIncrement = rsmd.isAutoIncrement(i + 1); cmd.caseSensitive = rsmd.isCaseSensitive(i + 1); cmd.catalogName = rsmd.getCatalogName(i + 1); cmd.columnClassName = rsmd.getColumnClassName(i + 1); cmd.columnDisplaySize = rsmd.getColumnDisplaySize(i + 1); cmd.columnLabel = rsmd.getColumnLabel(i + 1); cmd.columnName = rsmd.getColumnLabel(i + 1); if ((cmd.columnName == null) || (cmd.columnName.length() == 0)) { // If the columnName is null or an empty string then we won't be able // to access it in a CFML page. To work around this set the name to // "Computed_Column_X". This particular problem was seen with Informix // stored procedures that return return values as a result set. This // is // also compatible with CF5/MX. cmd.columnName = "Computed_Column_" + (i + 1); } cmd.columnType = rsmd.getColumnType(i + 1); cmd.columnTypeName = rsmd.getColumnTypeName(i + 1); cmd.currency = rsmd.isCurrency(i + 1); cmd.definitelyWriteable = rsmd.isDefinitelyWritable(i + 1); cmd.nullable = rsmd.isNullable(i + 1); try { cmd.precision = rsmd.getPrecision(i + 1); } catch (NumberFormatException e) { // Oracle does this for LOB types cmd.precision = Integer.MAX_VALUE; } cmd.readOnly = rsmd.isReadOnly(i + 1); cmd.scale = rsmd.getScale(i + 1); cmd.schemaName = rsmd.getSchemaName(i + 1); cmd.searchable = rsmd.isSearchable(i + 1); cmd.signed = rsmd.isSigned(i + 1); cmd.tableName = rsmd.getTableName(i + 1); cmd.writable = rsmd.isWritable(i + 1); columnList[i] = cmd; columnNameCache.put(cmd.columnName, new Integer(i + 1)); } } catch (SQLException e) { // If you see the MySQL JDBC driver throwing an exception with text like // "Can't create/write // to file 'C:\WINDOWS\TEMP\#sql_2ddc_0.MYD' (Errcode: 13)" then you may // need to disable your // antivirus program. I saw this problem with McAfee - Paul. cfEngine.log(e.getMessage()); } } public cfQueryResultSetMetaData(String[] columnNames, int[] columnTypes) { columnList = new ColumnMetaData[columnNames.length]; isColumnTypeSet = columnTypes != null; for (int i = 0; i < columnNames.length; i++) { columnList[i] = new ColumnMetaData(columnNames[i]); if (columnTypes != null) { columnList[i].columnType = columnTypes[i]; switch (columnTypes[i]) { case Types.VARCHAR: columnList[i].columnTypeName = "VARCHAR"; break; case Types.BIGINT: columnList[i].columnTypeName = "BIGINT"; break; case Types.BINARY: columnList[i].columnTypeName = "BINARY"; break; case Types.BIT: columnList[i].columnTypeName = "BIT"; break; case Types.DATE: columnList[i].columnTypeName = "DATE"; break; case Types.TIME: columnList[i].columnTypeName = "TIME"; break; case Types.INTEGER: columnList[i].columnTypeName = "INTEGER"; break; case Types.DOUBLE: columnList[i].columnTypeName = "DOUBLE"; break; case Types.DECIMAL: columnList[i].columnTypeName = "DECIMAL"; break; default: columnList[i].columnTypeName = "OTHER"; break; } } columnNameCache.put(columnNames[i], new Integer(i + 1)); } } public boolean isColumnTypesSet() { return this.isColumnTypeSet; } /** * WARNING! The performance of this method is critical to overall system * performance. Do not make any changes to this method without doing * before-and-after timing measurements to make sure you have not decreased * performance. */ public int getColumnIndex(String columnName) { // return a 1-based index for // the column Integer columnIndexObj = columnNameCache.get(columnName); if (columnIndexObj != null) { return columnIndexObj.intValue(); } return 0; } public Object clone() { return new cfQueryResultSetMetaData(this); } public String[] getColumnNames() { String[] columnNames = new String[columnList.length]; for (int i = 0; i < columnList.length; i++) columnNames[i] = columnList[i].columnName; return columnNames; } public int[] getColumnTypes() { int[] columnTypes = new int[columnList.length]; for (int i = 0; i < columnList.length; i++) columnTypes[i] = columnList[i].columnType; return columnTypes; } public synchronized void deleteColumn(String columnName){ int columnIndex = getColumnIndex( columnName ) - 1; if ( columnIndex < 0 ) return; // Clear down the cache; we may need to rebuild it columnNameCache.clear(); if ( columnList.length == 1 ){ columnList = new ColumnMetaData[0]; }else{ ColumnMetaData[] columnListTemp = new ColumnMetaData[ columnList.length - 1 ]; if ( columnIndex > 0 ){ System.arraycopy( columnList, 0, columnListTemp, 0, columnIndex ); } System.arraycopy( columnList, columnIndex+1, columnListTemp, columnIndex, columnList.length - columnIndex - 1 ); columnList = columnListTemp; // rebuild the columnNameCache for ( int x=0; x < columnList.length; x++ ){ columnNameCache.put( columnList[x].columnName, new Integer(x + 1) ); } } } public synchronized int addColumn(String columnName) { return addColumn(new ColumnMetaData(columnName)); } public synchronized int addColumn(String columnName, int type) { return addColumn(new ColumnMetaData(columnName, type)); } private synchronized int addColumn(ColumnMetaData col) { ColumnMetaData[] temp = new ColumnMetaData[columnList.length + 1]; System.arraycopy(columnList, 0, temp, 0, columnList.length); temp[columnList.length] = col; columnNameCache.put(col.columnName, new Integer(columnList.length + 1)); columnList = temp; return columnList.length; // 1-based index of column just added } /* * This is for renaming a column */ public boolean renameColumn(String oldcolumnName, String newcolumnName) { int columnIndex = getColumnIndex( oldcolumnName ); if ( columnIndex == 0 ) return false; // make sure the new column doesn't already exist int chkcolumnIndex = getColumnIndex( newcolumnName ); if ( chkcolumnIndex > 0 ) return false; // Update the cache columnNameCache.remove(oldcolumnName); columnNameCache.put(newcolumnName, new Integer(columnIndex) ); columnList[columnIndex-1].columnName = newcolumnName; return false; } /** * Takes a 1-based column index, does a range check, and returns a 0-based * (private) index. Throws an exception for index-out-of-range. */ private int internalColumnIndex(int column) throws SQLException { if ((column < 1) || (column > columnList.length)) throw new SQLException("index out of range: " + column); return (column - 1); } private class ColumnMetaData implements java.io.Serializable { private static final long serialVersionUID = 1L; private boolean autoIncrement; private boolean caseSensitive; private boolean currency; private int nullable; private boolean signed; private boolean searchable; private int columnDisplaySize; private String columnLabel; private String columnName; private String columnClassName; private String schemaName; private int precision; private int scale; private String tableName; private String catalogName; private int columnType = java.sql.Types.NULL; // Default it to null private String columnTypeName; private boolean readOnly; private boolean writable; private boolean definitelyWriteable; private ColumnMetaData() { } private ColumnMetaData(String _columnName) { columnLabel = _columnName; columnName = _columnName; } private ColumnMetaData(String _columnName, int _type) { columnLabel = _columnName; columnName = _columnName; columnType = _type; } } /************************************************************************** * * Methods below here implement the java.sql.ResultSetMetaData interface. * **************************************************************************/ public int getColumnCount() { return columnList.length; } public boolean isAutoIncrement(int column) throws SQLException { return columnList[internalColumnIndex(column)].autoIncrement; } public boolean isCaseSensitive(int column) throws SQLException { return columnList[internalColumnIndex(column)].caseSensitive; } public boolean isCurrency(int column) throws SQLException { return columnList[internalColumnIndex(column)].currency; } public int isNullable(int column) throws SQLException { return columnList[internalColumnIndex(column)].nullable; } public boolean isSigned(int column) throws SQLException { return columnList[internalColumnIndex(column)].signed; } public int getColumnDisplaySize(int column) throws SQLException { return columnList[internalColumnIndex(column)].columnDisplaySize; } public String getColumnLabel(int column) throws SQLException { return columnList[internalColumnIndex(column)].columnLabel; } public String getColumnName(int column) throws SQLException { return columnList[internalColumnIndex(column)].columnName; } public String getColumnClassName(int column) throws SQLException { return columnList[internalColumnIndex(column)].columnClassName; } public String getSchemaName(int column) throws SQLException { return columnList[internalColumnIndex(column)].schemaName; } public int getPrecision(int column) throws SQLException { return columnList[internalColumnIndex(column)].precision; } public int getScale(int column) throws SQLException { return columnList[internalColumnIndex(column)].scale; } public String getTableName(int column) throws SQLException { return columnList[internalColumnIndex(column)].tableName; } public String getCatalogName(int column) throws SQLException { return columnList[internalColumnIndex(column)].catalogName; } public int getColumnType(int column) throws SQLException { return columnList[internalColumnIndex(column)].columnType; } public String getColumnTypeName(int column) throws SQLException { return columnList[internalColumnIndex(column)].columnTypeName; } public boolean isReadOnly(int column) throws SQLException { return columnList[internalColumnIndex(column)].readOnly; } public boolean isWritable(int column) throws SQLException { return columnList[internalColumnIndex(column)].writable; } public boolean isDefinitelyWritable(int column) throws SQLException { return columnList[internalColumnIndex(column)].definitelyWriteable; } public boolean isSearchable(int column) throws SQLException { return columnList[internalColumnIndex(column)].searchable; } /************************************************************************** * * Methods below here are used to fill in the column information. * **************************************************************************/ public void setAutoIncrement(int column, boolean b) throws SQLException { columnList[internalColumnIndex(column)].autoIncrement = b; } public void setNullable(int column, int i) throws SQLException { columnList[internalColumnIndex(column)].nullable = i; } public void setColumnLabel(int column, String label) throws SQLException { columnList[internalColumnIndex(column)].columnLabel = label; } public void setColumnName(int column, String name) throws SQLException { columnList[internalColumnIndex(column)].columnName = name; columnNameCache.put(name, new Integer(column)); } public void setColumnType(int column, int type) throws SQLException { columnList[internalColumnIndex(column)].columnType = type; } public void setReadOnly(int column, boolean b) throws SQLException { columnList[internalColumnIndex(column)].readOnly = b; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { throw new SQLException("method not supported"); } @Override public <T> T unwrap(Class<T> iface) throws SQLException { throw new SQLException("method not supported"); } }