package railo.runtime.type;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URL;
import java.nio.charset.Charset;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;

import railo.commons.db.DBUtil;
import railo.commons.io.IOUtil;
import railo.commons.lang.StringUtil;
import railo.commons.sql.SQLUtil;
import railo.loader.engine.CFMLEngineFactory;
import railo.runtime.PageContext;
import railo.runtime.config.NullSupportHelper;
import railo.runtime.converter.ScriptConverter;
import railo.runtime.db.CFTypes;
import railo.runtime.db.DataSourceUtil;
import railo.runtime.db.DatasourceConnection;
import railo.runtime.db.DatasourceConnectionImpl;
import railo.runtime.db.SQL;
import railo.runtime.db.SQLCaster;
import railo.runtime.db.SQLItem;
import railo.runtime.dump.DumpData;
import railo.runtime.dump.DumpProperties;
import railo.runtime.engine.ThreadLocalPageContext;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.DatabaseException;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.exp.PageRuntimeException;
import railo.runtime.interpreter.CFMLExpressionInterpreter;
import railo.runtime.op.Caster;
import railo.runtime.op.Decision;
import railo.runtime.op.Duplicator;
import railo.runtime.op.ThreadLocalDuplication;
import railo.runtime.op.date.DateCaster;
import railo.runtime.query.caster.Cast;
import railo.runtime.type.comparator.NumberSortRegisterComparator;
import railo.runtime.type.comparator.SortRegister;
import railo.runtime.type.comparator.SortRegisterComparator;
import railo.runtime.type.dt.DateTime;
import railo.runtime.type.dt.DateTimeImpl;
import railo.runtime.type.it.CollectionIterator;
import railo.runtime.type.it.EntryIterator;
import railo.runtime.type.it.ForEachQueryIterator;
import railo.runtime.type.it.KeyIterator;
import railo.runtime.type.it.StringIterator;
import railo.runtime.type.sql.BlobImpl;
import railo.runtime.type.sql.ClobImpl;
import railo.runtime.type.util.ArrayUtil;
import railo.runtime.type.util.CollectionUtil;
import railo.runtime.type.util.KeyConstants;
import railo.runtime.type.util.ListUtil;
import railo.runtime.type.util.MemberUtil;
import railo.runtime.type.util.QueryUtil;

/**
 * implementation of the query interface
 */
/**
 * 
 */
public class QueryImpl implements Query,Objects {

	private static final long serialVersionUID = 1035795427320192551L; // do not chnage


	/**
	 * @return the template
	 */
	public String getTemplate() {
		return template;
	}

	public static final Collection.Key GENERATED_KEYS = KeyImpl.intern("GENERATED_KEYS");
	public static final Collection.Key GENERATEDKEYS = KeyImpl.intern("GENERATEDKEYS");
	
	
	
	//private static int count=0;
	private QueryColumnImpl[] columns;
	private Collection.Key[] columnNames;
	private SQL sql;
	private ArrayInt arrCurrentRow=new ArrayInt();
	private int recordcount=0;
	private int columncount;
	private long exeTime=0;
    private boolean isCached=false;
    private String name;
	private int updateCount;
    private QueryImpl generatedKeys;
	private String template;
	
	
	@Override
	public int executionTime() {
		return (int) exeTime;
	}

	/**
	 * create a QueryImpl from a SQL Resultset
     * @param result SQL Resultset
	 * @param maxrow
	 * @param name 
	 * @throws PageException
     */
    public QueryImpl(ResultSet result, int maxrow, String name, TimeZone tz) throws PageException {
    	this.name=name;
        //Stopwatch stopwatch=new Stopwatch();
		//stopwatch.start();
		long start=System.nanoTime();
    	try {
            fillResult(null,result,maxrow,false,false,tz);
        } catch (SQLException e) {
            throw new DatabaseException(e,null);
        } catch (IOException e) {
            throw Caster.toPageException(e);
        }
		exeTime=System.nanoTime()-start;
    }
    
    /**
     * Constructor of the class
     * only for internal usage (cloning/deserialize)
     */
    public QueryImpl() {
    }
    
    
    public QueryImpl(ResultSet result, String name,TimeZone tz) throws PageException {
		this.name=name;
        
		try {	
		    fillResult(null,result,-1,true,false,tz);
		} 
		catch (SQLException e) {
			throw new DatabaseException(e,null);
		} 
		catch (Exception e) {
		    throw Caster.toPageException(e);
		} 
	}
	
	/**
	 * constructor of the class, to generate a resultset from a sql query
	 * @param dc Connection to a database
	 * @param name 
	 * @param sql sql to execute
	 * @param maxrow maxrow for the resultset
	 * @throws PageException
	 */	
    public QueryImpl(PageContext pc, DatasourceConnection dc,SQL sql,int maxrow, int fetchsize,int timeout, String name) throws PageException {
    	this(pc,dc, sql, maxrow, fetchsize, timeout, name,null,false,true);
    }
    

    public QueryImpl(PageContext pc, DatasourceConnection dc,SQL sql,int maxrow, int fetchsize,int timeout, String name,String template) throws PageException {
    	this(pc,dc, sql, maxrow, fetchsize, timeout, name,template,false,true);
    }
    
	public QueryImpl(PageContext pc, DatasourceConnection dc,SQL sql,int maxrow, int fetchsize,int timeout, String name,String template,boolean createUpdateData, boolean allowToCachePreperadeStatement) throws PageException {
		this.name=name;
		this.template=template;
        this.sql=sql;
		TimeZone tz = ThreadLocalPageContext.getTimeZone(pc);
        //ResultSet result=null;
		Statement stat=null;
		// check SQL Restrictions
		if(dc.getDatasource().hasSQLRestriction()) {
			QueryUtil.checkSQLRestriction(dc,sql);
        }
		// check if datasource support Generated Keys
		boolean createGeneratedKeys=createUpdateData;
        if(createUpdateData){
        	DatasourceConnectionImpl dci=(DatasourceConnectionImpl) dc;
        	if(!dci.supportsGetGeneratedKeys())createGeneratedKeys=false;
        }

        
		//Stopwatch stopwatch=new Stopwatch();
        long start=System.nanoTime();
		//stopwatch.start();
		boolean hasResult=false;
		//boolean closeStatement=true;
		try {	
			SQLItem[] items=sql.getItems();
			if(items.length==0) {
		    	stat=dc.getConnection().createStatement();
		        setAttributes(stat,maxrow,fetchsize,timeout);
		     // some driver do not support second argument
		        //hasResult=createGeneratedKeys?stat.execute(sql.getSQLString(),Statement.RETURN_GENERATED_KEYS):stat.execute(sql.getSQLString());
		        hasResult=QueryUtil.execute(pc,stat,createGeneratedKeys,sql);
	        }
	        else {
	        	// some driver do not support second argument
	        	PreparedStatement preStat = dc.getPreparedStatement(sql, createGeneratedKeys,allowToCachePreperadeStatement);
	        	//closeStatement=false;
	        	stat=preStat;
	            setAttributes(preStat,maxrow,fetchsize,timeout);
	            setItems(ThreadLocalPageContext.getTimeZone(pc),preStat,items);
		        hasResult=QueryUtil.execute(pc,preStat);    
	        }
			int uc;
			ResultSet res;
			do {
				if(hasResult) {
					res=stat.getResultSet();
					if(fillResult(dc,res, maxrow, true,createGeneratedKeys,tz))break;
				}
				else if((uc=setUpdateCount(stat))!=-1){
					if(uc>0 && createGeneratedKeys)setGeneratedKeys(dc, stat,tz);
				}
				else break;
				try{
					hasResult=stat.getMoreResults(Statement.CLOSE_CURRENT_RESULT);
				}
				catch(Throwable t){
					break;
				}
			}
			while(true);
		} 
		catch (SQLException e) {
			throw new DatabaseException(e,sql,dc);
		} 
		catch (Throwable e) {
			throw Caster.toPageException(e);
		}
        finally {
        	//if(closeStatement)
        		DBUtil.closeEL(stat);
        }  
		exeTime=System.nanoTime()-start;

		if(columncount==0) {
			if(columnNames==null) columnNames=new Collection.Key[0];
			if(columns==null) columns=new QueryColumnImpl[0];
		}
	}
	
	private int setUpdateCount(Statement stat)  {
		try{
			int uc=stat.getUpdateCount();
			if(uc>-1){
				updateCount+=uc;
				return uc;
			}
		}
		catch(Throwable t){}
		return -1;
	}
	
	private boolean setGeneratedKeys(DatasourceConnection dc,Statement stat, TimeZone tz)  {
		try{
			ResultSet rs = stat.getGeneratedKeys();
			setGeneratedKeys(dc, rs,tz);
			return true;
		}
		catch(Throwable t) {t.printStackTrace();
			return false;
		}
	}
	
	private void setGeneratedKeys(DatasourceConnection dc,ResultSet rs, TimeZone tz) throws PageException  {
		generatedKeys=new QueryImpl(rs,"",tz);
		
		// ACF compatibility action 
		if(generatedKeys.getColumnCount()==1 && DataSourceUtil.isMSSQL(dc)) {
			generatedKeys.renameEL(GENERATED_KEYS,KeyConstants._IDENTITYCOL);
			generatedKeys.renameEL(GENERATEDKEYS,KeyConstants._IDENTITYCOL);
			generatedKeys.renameEL(KeyConstants._ID,KeyConstants._IDENTITYCOL);
		}
	}
	
	/*private void setUpdateData(Statement stat, boolean createGeneratedKeys, boolean createUpdateCount)  {
		
		// update Count
		if(createUpdateCount){
			try{
				updateCount=stat.getUpdateCount();
			}
			catch(Throwable t){
				t.printStackTrace();
			}
		}
		// generated keys
		if(createGeneratedKeys){
			try{
				ResultSet rs = stat.getGeneratedKeys();
				generatedKeys=new QueryImpl(rs,"");
			}
			catch(Throwable t){
				t.printStackTrace();
			}
		}
	}*/


	private void setItems(TimeZone tz,PreparedStatement preStat, SQLItem[] items) throws DatabaseException, PageException, SQLException {
		for(int i=0;i<items.length;i++) {
            SQLCaster.setValue(tz,preStat,i+1,items[i]);
        }
	}

	public int getUpdateCount() {
		return updateCount;
	}
	public Query getGeneratedKeys() {
		return generatedKeys;
	}

	private void setAttributes(Statement stat,int maxrow, int fetchsize,int timeout) throws SQLException {
		if(maxrow>-1) stat.setMaxRows(maxrow);
        if(fetchsize>0)stat.setFetchSize(fetchsize);
        if(timeout>0)stat.setQueryTimeout(timeout);
	}

    private boolean fillResult(DatasourceConnection dc, ResultSet result, int maxrow, boolean closeResult,boolean createGeneratedKeys, TimeZone tz) throws SQLException, IOException, PageException {
    	if(result==null) return false;
    	try {
	    	recordcount=0;
			ResultSetMetaData meta = result.getMetaData();
			columncount=meta.getColumnCount();
			
		// set header arrays
			Collection.Key[] tmpColumnNames = new Collection.Key[columncount];
			int count=0;
			Collection.Key key;
			String columnName;
			for(int i=0;i<columncount;i++) {
				columnName=QueryUtil.getColumnName(meta,i+1);
				if(StringUtil.isEmpty(columnName))columnName="column_"+i;
				key=KeyImpl.init(columnName);
				int index=getIndexFrom(tmpColumnNames,key,0,i);
				if(index==-1) {
					tmpColumnNames[i]=key;
					count++;
				}
			}
			
	
			columncount=count;
			columnNames=new Collection.Key[columncount];
			columns=new QueryColumnImpl[columncount];
			Cast[] casts = new Cast[columncount];
			
		// get all used ints
			int[] usedColumns=new int[columncount];
			count=0;
			for(int i=0;i<tmpColumnNames.length;i++) {
				if(tmpColumnNames[i]!=null) {
					usedColumns[count++]=i;
				}
			}	
						
		// set used column names
			int[] types=new int[columns.length];
			for(int i=0;i<usedColumns.length;i++) {
	            columnNames[i]=tmpColumnNames[usedColumns[i]];
	            columns[i]=new QueryColumnImpl(this,columnNames[i],types[i]=meta.getColumnType(usedColumns[i]+1));
	            
	            if(types[i]==Types.TIMESTAMP)	casts[i]=Cast.TIMESTAMP;
	            else if(types[i]==Types.TIME)	casts[i]=Cast.TIME;
	            else if(types[i]==Types.DATE)	casts[i]=Cast.DATE;
	            else if(types[i]==Types.CLOB)	casts[i]=Cast.CLOB;
	            else if(types[i]==Types.BLOB)	casts[i]=Cast.BLOB;
	            else if(types[i]==Types.BIT)	casts[i]=Cast.BIT;
	            else if(types[i]==Types.ARRAY)	casts[i]=Cast.ARRAY;
	            else if(types[i]==Types.BIGINT)	casts[i]=Cast.BIGINT;
	            //else if(types[i]==Types.TINYINT)	casts[i]=Cast.ARRAY;
	            
	            else if(types[i]==CFTypes.OPAQUE){
	            	if(SQLUtil.isOracle(result.getStatement().getConnection()))
	            		casts[i]=Cast.ORACLE_OPAQUE;
	            	else 
	            		casts[i]=Cast.OTHER;
					
	            }
	            else casts[i]=Cast.OTHER;
			}
			
			if(createGeneratedKeys && columncount==1 && columnNames[0].equals(GENERATED_KEYS) && dc!=null && DataSourceUtil.isMSSQLDriver(dc)) {
				columncount=0;
				columnNames=null;
				columns=null;
				setGeneratedKeys(dc, result,tz);
				return false;
			}
			
	
		// fill data
			//Object o;
			while(result.next()) {
				if(maxrow>-1 && recordcount>=maxrow) {
					break;
				}
				for(int i=0;i<usedColumns.length;i++) {
				    columns[i].add(casts[i].toCFType(tz,types[i], result, usedColumns[i]+1));
				}
				++recordcount;
			}
    	}
    	finally {
    		if(closeResult)IOUtil.closeEL(result);
    	}
		
		return true;
	}

    private Object toBytes(Blob blob) throws IOException, SQLException {
		return IOUtil.toBytes((blob).getBinaryStream());
	}

	private static Object toString(Clob clob) throws IOException, SQLException {
		return IOUtil.toString(clob.getCharacterStream());
	}

    private static int getIndexFrom(Collection.Key[] tmpColumnNames, Collection.Key key, int from, int to) {
		for(int i=from;i<to;i++) {
			if(tmpColumnNames[i]!=null && tmpColumnNames[i].equalsIgnoreCase(key))return i;
		}
		return -1;
	}

	/**
	 * constructor of the class, to generate a empty resultset (no database execution)
	 * @param strColumns columns for the resultset
	 * @param rowNumber count of rows to generate (empty fields)
	 * @param name 
	 * @deprecated use instead <code>QueryImpl(Collection.Key[] columnKeys, int rowNumber,String name)</code>
	 */
	public QueryImpl(String[] strColumns, int rowNumber,String name) {
        this.name=name;
        columncount=strColumns.length;
		recordcount=rowNumber;
		columnNames=new Collection.Key[columncount];
		columns=new QueryColumnImpl[columncount];
		for(int i=0;i<strColumns.length;i++) {
			columnNames[i]=KeyImpl.init(strColumns[i].trim());
			columns[i]=new QueryColumnImpl(this,columnNames[i],Types.OTHER,recordcount);
		}
	}

	/**
	 * constructor of the class, to generate a empty resultset (no database execution)
	 * @param strColumns columns for the resultset
	 * @param rowNumber count of rows to generate (empty fields)
	 * @param name 
	 */
	public QueryImpl(Collection.Key[] columnKeys, int rowNumber,String name) throws DatabaseException {
		this.name=name;
        columncount=columnKeys.length;
		recordcount=rowNumber;
		columnNames=new Collection.Key[columncount];
		columns=new QueryColumnImpl[columncount];
		for(int i=0;i<columnKeys.length;i++) {
			columnNames[i]=columnKeys[i];
			columns[i]=new QueryColumnImpl(this,columnNames[i],Types.OTHER,recordcount);
		}
		validateColumnNames(columnNames);
	}
	
	/**
	 * constructor of the class, to generate a empty resultset (no database execution)
	 * @param strColumns columns for the resultset
	 * @param strTypes array of the types
	 * @param rowNumber count of rows to generate (empty fields)
	 * @param name 
	 * @throws DatabaseException 
	 */
	public QueryImpl(String[] strColumns, String[] strTypes, int rowNumber, String name) throws DatabaseException {
        this.name=name;
        columncount=strColumns.length;
		if(strTypes.length!=columncount) throw new DatabaseException("columns and types has not the same count",null,null,null);
		recordcount=rowNumber;
		columnNames=new Collection.Key[columncount];
		columns=new QueryColumnImpl[columncount];
		for(int i=0;i<strColumns.length;i++) {
			columnNames[i]=KeyImpl.init(strColumns[i].trim());
			columns[i]=new QueryColumnImpl(this,columnNames[i],SQLCaster.toIntType(strTypes[i]),recordcount);
		}
	}
	
	/**
	 * constructor of the class, to generate a empty resultset (no database execution)
	 * @param strColumns columns for the resultset
	 * @param strTypes array of the types
	 * @param rowNumber count of rows to generate (empty fields)
	 * @param name 
	 * @throws DatabaseException 
	 */
	public QueryImpl(Collection.Key[] columnNames, String[] strTypes, int rowNumber, String name) throws DatabaseException {
        this.name=name;
        this.columnNames=columnNames;
        columncount=columnNames.length;
		if(strTypes.length!=columncount) throw new DatabaseException("columns and types has not the same count",null,null,null);
		recordcount=rowNumber;
		columns=new QueryColumnImpl[columncount];
		for(int i=0;i<columnNames.length;i++) {
			columns[i]=new QueryColumnImpl(this,columnNames[i],SQLCaster.toIntType(strTypes[i]),recordcount);
		}
		validateColumnNames(columnNames);
	}
	
	/**
	 * constructor of the class, to generate a empty resultset (no database execution)
	 * @param arrColumns columns for the resultset
	 * @param rowNumber count of rows to generate (empty fields)
	 * @param name 
	 * @throws DatabaseException 
	 */
	public QueryImpl(Array arrColumns, int rowNumber, String name) throws DatabaseException {
        this.name=name;
        columncount=arrColumns.size();
		recordcount=rowNumber;
		columnNames=new Collection.Key[columncount];
		columns=new QueryColumnImpl[columncount];
		for(int i=0;i<columncount;i++) {
			columnNames[i]=KeyImpl.init(arrColumns.get(i+1,"").toString().trim());
			columns[i]=new QueryColumnImpl(this,columnNames[i],Types.OTHER,recordcount);
		}
		validateColumnNames(columnNames);
	}

	/**
	 * constructor of the class, to generate a empty resultset (no database execution)
	 * @param arrColumns columns for the resultset
	 * @param arrTypes type of the columns
	 * @param rowNumber count of rows to generate (empty fields)
	 * @param name 
	 * @throws PageException
	 */
	public QueryImpl(Array arrColumns, Array arrTypes, int rowNumber, String name) throws PageException {
        this.name=name;
        columncount=arrColumns.size();
		if(arrTypes.size()!=columncount) throw new DatabaseException("columns and types has not the same count",null,null,null);
		recordcount=rowNumber;
		columnNames=new Collection.Key[columncount];
		columns=new QueryColumnImpl[columncount];
		for(int i=0;i<columncount;i++) {
			columnNames[i]=KeyImpl.init(arrColumns.get(i+1,"").toString().trim());
			columns[i]=new QueryColumnImpl(this,columnNames[i],SQLCaster.toIntType(Caster.toString(arrTypes.get(i+1,""))),recordcount);
		}
		validateColumnNames(columnNames);
	}

	/**
	 * constructor of the class
	 * @param columnNames columns definition as String Array
	 * @param arrColumns values
	 * @param name 
	 * @throws DatabaseException
	 */

	public QueryImpl(String[] strColumnNames, Array[] arrColumns, String name) throws DatabaseException {
		this(_toKeys(strColumnNames),arrColumns,name);		
	}	
	
	private static void validateColumnNames(Key[] columnNames) throws DatabaseException {
		Set<String> testMap=new HashSet<String>();
		for(int i=0	;i<columnNames.length;i++) {
			
			// Only allow column names that are valid variable name
			//if(!Decision.isSimpleVariableName(columnNames[i]))
			//	throw new DatabaseException("invalid column name ["+columnNames[i]+"] for query", "column names must start with a letter and can be followed by letters numbers and underscores [_]. RegExp:[a-zA-Z][a-zA-Z0-9_]*",null,null,null);
			
			if(testMap.contains(columnNames[i].getLowerString()))
				throw new DatabaseException("invalid parameter for query, ambiguous column name "+columnNames[i],"columnNames: "+ListUtil.arrayToListTrim( _toStringKeys(columnNames),","),null,null);
			testMap.add(columnNames[i].getLowerString());
		}
	}
	

	private static Collection.Key[] _toKeys(String[] strColumnNames) {
		Collection.Key[] columnNames=new Collection.Key[strColumnNames.length];
		for(int i=0	;i<columnNames.length;i++) {
			columnNames[i]=KeyImpl.init(strColumnNames[i].trim());
		}
		return columnNames;
	}
	private static String[] _toStringKeys(Collection.Key[] columnNames) {
		String[] strColumnNames=new String[columnNames.length];
		for(int i=0	;i<strColumnNames.length;i++) {
			strColumnNames[i]=columnNames[i].getString();
		}
		return strColumnNames;
	}
	
	/*public QueryImpl(Collection.Key[] columnNames, QueryColumn[] columns, String name,long exeTime, boolean isCached,SQL sql) throws DatabaseException {
		this.columnNames=columnNames;
		this.columns=columns;
		this.exeTime=exeTime;
		this.isCached=isCached;
		this.name=name;
		this.columncount=columnNames.length;
		this.recordcount=columns.length==0?0:columns[0].size();
		this.sql=sql;
		
	}*/

	public QueryImpl(Collection.Key[] columnNames, Array[] arrColumns, String name) throws DatabaseException {
        this.name=name;
        
        if(columnNames.length!=arrColumns.length)
			throw new DatabaseException("invalid parameter for query, not the same count from names and columns","names:"+columnNames.length+";columns:"+arrColumns.length,null,null);
		int len=0;
		columns=new QueryColumnImpl[arrColumns.length];
		if(arrColumns.length>0) {
		// test columns
			len=arrColumns[0].size();
			for(int i=0;i<arrColumns.length;i++) {
				if(arrColumns[i].size()!=len)
					throw new DatabaseException("invalid parameter for query, all columns must have the same size","column[1]:"+len+"<>column["+(i+1)+"]:"+arrColumns[i].size(),null,null);
				//columns[i]=new QueryColumnTypeFlex(arrColumns[i]);
				columns[i]=new QueryColumnImpl(this,columnNames[i],arrColumns[i],Types.OTHER);
			}
		// test keys
			validateColumnNames(columnNames);
		}
		
		columncount=columns.length;
		recordcount=len;
		this.columnNames=columnNames;
	}
	
    /**
     * constructor of the class
     * @param columnList
     * @param data
     * @param name 
     * @throws DatabaseException 
     */
    public QueryImpl(String[] strColumnList, Object[][] data,String name) throws DatabaseException {
    	
        this(toCollKeyArr(strColumnList),data.length,name);
        
        for(int iRow=0;iRow<data.length;iRow++) {
            Object[] row=data[iRow];
            for(int iCol=0;iCol<row.length;iCol++) {
                //print.ln(columnList[iCol]+":"+iRow+"="+row[iCol]);
                setAtEL(columnNames[iCol],iRow+1,row[iCol]);
            }
        }
    }

    private static Collection.Key[] toCollKeyArr(String[] strColumnList) {
    	Collection.Key[] columnList=new Collection.Key[strColumnList.length];
		for(int i=0	;i<columnList.length;i++) {
			columnList[i]=KeyImpl.init(strColumnList[i].trim());
		}
		return columnList;
	}

	@Override
	public int size() {
		return columncount;
	}

	@Override
	public Collection.Key[] keys() {
		return columnNames;
	}

	@Override
	public Object removeEL(Collection.Key key) {
		return setEL(key,null);
	}

	@Override
	public Object remove(Collection.Key key) throws PageException {
		return set(key,null);
	}

	@Override
	public void clear() {
		for(int i=0;i<columns.length;i++) {
			columns[i].clear();
        }
        recordcount=0;
	}

	@Override
	public Object get(String key, Object defaultValue) {
		return getAt(key,
				arrCurrentRow.get(getPid(), 1),
				defaultValue);
	}

	//private static int pidc=0;
	private int getPid() {
		
		PageContext pc = ThreadLocalPageContext.get();
		if(pc==null) {
			pc=CFMLEngineFactory.getInstance().getThreadPageContext();
			if(pc==null)throw new RuntimeException("cannot get pid for current thread");
		}
		return pc.getId();
	}

	@Override
	public Object get(Collection.Key key, Object defaultValue) {
		return getAt(key,
				arrCurrentRow.get(getPid(), 1),
				defaultValue);
	}

	@Override
	public Object get(String key) throws PageException {
		return getAt(key,
				arrCurrentRow.get(getPid(), 1)
				);
	}

	@Override
	public Object get(Collection.Key key) throws PageException {
		return getAt(key,
				arrCurrentRow.get(getPid(), 1)
				);
	}

	@Override
	public Object getAt(String key, int row, Object defaultValue) {
		return getAt(KeyImpl.init(key), row, defaultValue);
	}

	public Object getAt(Collection.Key key, int row, Object defaultValue) {
		int index=getIndexFromKey(key);
		if(index!=-1) {
			return columns[index].get(row,defaultValue);
		}
		if(key.length()>=10) {
	        if(key.equals(KeyConstants._RECORDCOUNT)) return new Double(getRecordcount());
	        if(key.equals(KeyConstants._CURRENTROW)) return new Double(row);
	        if(key.equals(KeyConstants._COLUMNLIST)) return getColumnlist(true);
		}
        return defaultValue;
	}
	
	@Override
	public Object getAt(String key, int row) throws PageException {
		return getAt(KeyImpl.init(key), row);
	}

	@Override
	public Object getAt(Collection.Key key, int row) throws PageException {
		int index=getIndexFromKey(key);
		if(index!=-1) {
			return columns[index].get(row, NullSupportHelper.empty());
		}
		if(key.length()>=10) {
        	if(key.equals(KeyConstants._RECORDCOUNT)) return new Double(getRecordcount());
        	if(key.equals(KeyConstants._CURRENTROW)) return new Double(row);
			if(key.equals(KeyConstants._COLUMNLIST)) return getColumnlist(true);
        }
		throw new DatabaseException("column ["+key+"] not found in query, columns are ["+getColumnlist(false)+"]",null,sql,null);
	}
	

    @Override
    public synchronized int removeRow(int row) throws PageException {
        //disconnectCache();
        
        for(int i=0;i<columns.length;i++) {
            columns[i].removeRow(row);
        }
        return --recordcount;
    }

    @Override
    public int removeRowEL(int row) {
        //disconnectCache();
        
        try {
            return removeRow(row);
        } catch (PageException e) {
            return recordcount;
        }
    }
    
    @Override
    public QueryColumn removeColumn(String key) throws DatabaseException {
        return removeColumn(KeyImpl.init(key));
    }

	@Override
	public QueryColumn removeColumn(Collection.Key key) throws DatabaseException {
		//disconnectCache();
        
        QueryColumn removed = removeColumnEL(key);
        if(removed==null) {
            if(key.equals(KeyConstants._RECORDCOUNT) || 
            		key.equals(KeyConstants._CURRENTROW) || 
            		key.equals(KeyConstants._COLUMNLIST))
                throw new DatabaseException("can't remove "+key+" this is not a row","existing rows are ["+getColumnlist(false)+"]",null,null);
            throw new DatabaseException("can't remove row ["+key+"], this row doesn't exist",
                    "existing rows are ["+getColumnlist(false)+"]",null,null);
        }
        return removed;
	}

    @Override
    public synchronized QueryColumn removeColumnEL(String key) {
        return removeColumnEL(KeyImpl.init(key));
    }

	public QueryColumn removeColumnEL(Collection.Key key) {
		//disconnectCache();
        
        int index=getIndexFromKey(key);
        if(index!=-1) {
            int current=0;
            QueryColumn removed=null;
            Collection.Key[] newColumnNames=new Collection.Key[columnNames.length-1];
            QueryColumnImpl[] newColumns=new QueryColumnImpl[columns.length-1];
            for(int i=0;i<columns.length;i++) {
                if(i==index) {
                    removed=columns[i];
                }
                else {
                    newColumnNames[current]=columnNames[i];
                    newColumns[current++]=columns[i];
                }
            }
            columnNames=newColumnNames;
            columns=newColumns;
            columncount--;
            return removed;
        }
        return null;
	}

	@Override
	public Object setEL(String key, Object value) {	
		return setEL(KeyImpl.init(key),value);	
	}

	@Override
	public Object setEL(Collection.Key key, Object value) {
		return setAtEL(key,
				arrCurrentRow.get(getPid(), 1),
				value);
	}
	
	@Override
	public Object set(String key, Object value) throws PageException {
		return set(KeyImpl.init(key),value);
	}

	@Override
	public Object set(Collection.Key key, Object value) throws PageException {
		return setAt(key,
				arrCurrentRow.get(getPid(), 1),
				value);
	}
    
    @Override
    public Object setAt(String key,int row, Object value) throws PageException {
    	return setAt(KeyImpl.init(key), row, value);
    }

    public Object setAt(Collection.Key key, int row, Object value) throws PageException {
		int index=getIndexFromKey(key);
        if(index!=-1) {
            return columns[index].set(row,value);
        }
        throw new DatabaseException("column ["+key+"] does not exist","columns are ["+getColumnlist(false)+"]",sql,null);
	}
    
    @Override
    public Object setAtEL(String key,int row, Object value) {
        return setAtEL(KeyImpl.init(key), row, value);
    }

	public Object setAtEL(Collection.Key key, int row, Object value) {
		int index=getIndexFromKey(key);
        if(index!=-1) {
            return columns[index].setEL(row,value);
        }
        return null;
	}

	@Override
	public boolean next() {
		return next(getPid());
	}

	@Override
	public boolean next(int pid) {
		if(recordcount>=(arrCurrentRow.set(pid,arrCurrentRow.get(pid,0)+1))) {
			return true;
		}
		arrCurrentRow.set(pid,0);
		return false;
	}

	@Override
	public void reset() {
		reset(getPid());
	}
	
	public void reset(int pid) {
		arrCurrentRow.set(pid,0);
	}

	@Override
	public int getRecordcount() {
		return recordcount;
	}
	
	@Override
	public int getCurrentrow(int pid) {
		return arrCurrentRow.get(pid,1);
	}


	/**
	 * return a string list of all columns
	 * @return string list
	 */
	public String getColumnlist(boolean upperCase) {
		//if(upperCase)upperCase=((ConfigImpl)ThreadLocalPageContext.getConfig()).getDotNotationUpperCase();
		StringBuffer sb=new StringBuffer();
		for(int i=0;i<columnNames.length;i++) {
			if(i>0)sb.append(',');
			sb.append(upperCase?columnNames[i].getUpperString():columnNames[i].getString());
		}
		return sb.toString();
	}
	public String getColumnlist() {
		return getColumnlist(true);
	}

	public boolean go(int index) {
		return go(index,getPid());
	}
	
	public boolean go(int index, int pid) {
		if(index>0 && index<=recordcount) {
			arrCurrentRow.set(pid, index);
			return true;
		}
		arrCurrentRow.set(pid, 0);
		return false;
	}

	@Override
	public boolean isEmpty() {
		return recordcount+columncount==0;
	}

	@Override
	public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
		return QueryUtil.toDumpData(this, pageContext, maxlevel, dp);
	}
	
	/**
	 * sorts a query by a column
	 * @param column colun to sort
	 * @throws PageException
	 */
	public void sort(String column) throws PageException {
		sort(column,Query.ORDER_ASC);
	}

	@Override
	public void sort(Collection.Key column) throws PageException {
		sort(column,Query.ORDER_ASC);
	}

	/**
	 * sorts a query by a column 
	 * @param strColumn column to sort
	 * @param order sort type (Query.ORDER_ASC or Query.ORDER_DESC)
	 * @throws PageException
	 */
	public synchronized void sort(String strColumn, int order) throws PageException {
		//disconnectCache();
        sort(getColumn(strColumn),order);
	}
	
	@Override
	public synchronized void sort(Collection.Key keyColumn, int order) throws PageException {
		//disconnectCache();
        sort(getColumn(keyColumn),order);
	}
	
	private void sort(QueryColumn column, int order) throws PageException {
        int type = column.getType();
		
		SortRegister[] arr= ArrayUtil.toSortRegisterArray(column);
		
		Arrays.sort(arr,
		(
				type==Types.BIGINT || 
				type==Types.BIT || 
				type==Types.INTEGER || 
				type==Types.SMALLINT || 
				type==Types.TINYINT	||
		        type==Types.DECIMAL || 
		        type==Types.DOUBLE || 
		        type==Types.NUMERIC || 
		        type==Types.REAL)?
		
		(Comparator)new NumberSortRegisterComparator(order==ORDER_ASC):(Comparator)new SortRegisterComparator(order==ORDER_ASC,true)
		);
		
		for(int i=0;i<columns.length;i++) {
			column=columns[i];
			int len=column.size();
			QueryColumnImpl newCol=new QueryColumnImpl(this,columnNames[i],columns[i].getType(),len);
			for(int y=1;y<=len;y++) {
				newCol.set(y,column.get(arr[y-1].getOldPosition()+1,null));
			}
			columns[i]=newCol;
		}
	}

	@Override
	 public synchronized boolean addRow(int count) {		
         //disconnectCache();
            
         for(int i=0;i<columns.length;i++) {
        	 QueryColumnPro column = columns[i];
		 	column.addRow(count);
		 }
		 recordcount+=count;
		 return true;
	 }
	 
	 @Override
	public boolean addColumn(String columnName, Array content) throws DatabaseException {
		return addColumn(columnName,content,Types.OTHER);
	 }

	public boolean addColumn(Collection.Key columnName, Array content) throws PageException {
		return addColumn(columnName,content,Types.OTHER);
	}
	
    @Override
    public synchronized boolean addColumn(String columnName, Array content, int type) throws DatabaseException {
		return addColumn(KeyImpl.init(columnName.trim()), content, type);
    }

	@Override
	public boolean addColumn(Collection.Key columnName, Array content, int type) throws DatabaseException {
		//disconnectCache();
        // TODO Meta type
		content=(Array) Duplicator.duplicate(content,false);
		
	 	if(getIndexFromKey(columnName)!=-1)
	 		throw new DatabaseException("column name ["+columnName.getString()+"] already exist",null,sql,null);
	 	if(content.size()!=getRecordcount()) {
	 		//throw new DatabaseException("array for the new column has not the same size like the query (arrayLen!=query.recordcount)");
	 		if(content.size()>getRecordcount()) addRow(content.size()-getRecordcount());
	 		else content.setEL(getRecordcount(),"");
	 	}
	 	QueryColumnImpl[] newColumns=new QueryColumnImpl[columns.length+1];
	 	Collection.Key[] newColumnNames=new Collection.Key[columns.length+1];
	 	boolean logUsage=false;
	 	for(int i=0;i<columns.length;i++) {
	 		newColumns[i]=columns[i];
	 		newColumnNames[i]=columnNames[i];
	 		if(!logUsage && columns[i] instanceof DebugQueryColumn) logUsage=true;
	 	}
	 	newColumns[columns.length]=new QueryColumnImpl(this,columnName,content,type);
	 	newColumnNames[columns.length]=columnName;
	 	columns=newColumns;
	 	columnNames=newColumnNames;
	 	
	 	columncount++;
	 	
	 	if(logUsage)enableShowQueryUsage();
	 	
		return true;
	}
	

	/* *
	 * if this query is still connected with cache (same query also in cache)
     * it will disconnetd from cache (clone object and add clone to cache)
	 */
	//protected void disconnectCache() {}

	
	@Override
    public Object clone() {
        return cloneQuery(true);
    }
    
    @Override
    public Collection duplicate(boolean deepCopy) {
        return cloneQuery(true);
    }
    

	
    
    /**
     * @return clones the query object
     */
    public QueryImpl cloneQuery(boolean deepCopy) {
        QueryImpl newResult=new QueryImpl();
        ThreadLocalDuplication.set(this, newResult);
        try{
	        if(columnNames!=null){
		        newResult.columnNames=new Collection.Key[columnNames.length];
		        newResult.columns=new QueryColumnImpl[columnNames.length];
		        for(int i=0;i<columnNames.length;i++) {
		        	newResult.columnNames[i]=columnNames[i];
		        	newResult.columns[i]=columns[i].cloneColumnImpl(deepCopy);
		        }
	        }
	        newResult.sql=sql;
	        newResult.template=template;
	        newResult.recordcount=recordcount;
	        newResult.columncount=columncount;
	        newResult.isCached=isCached;
	        newResult.name=name;
	        newResult.exeTime=exeTime;
	        newResult.updateCount=updateCount;
	        if(generatedKeys!=null)newResult.generatedKeys=generatedKeys.cloneQuery(false);
	        return newResult;
        }
        finally {
        	// ThreadLocalDuplication.remove(this); removed "remove" to catch sisters and brothers
        }
    }

	@Override
	public synchronized int[] getTypes() {
		int[] types=new int[columns.length];
		for(int i=0;i<columns.length;i++) {
		    types[i]=columns[i].getType();
		}
		return types;
	}
	
	@Override
	public synchronized Map<Collection.Key,String> getTypesAsMap() {
		
		Map<Collection.Key,String> map=new HashMap<Collection.Key,String>();
		for(int i=0;i<columns.length;i++) {
			map.put(columnNames[i],columns[i].getTypeAsString());
		}
		return map;
	}

	@Override
	public QueryColumn getColumn(String key) throws DatabaseException {
		return getColumn(KeyImpl.init(key.trim()));
	}

	@Override
	public QueryColumn getColumn(Collection.Key key) throws DatabaseException {
		int index=getIndexFromKey(key);
		if(index!=-1) return columns[index];
        
		if(key.length()>=10) {
        	if(key.equals(KeyConstants._RECORDCOUNT)) return new QueryColumnRef(this,key,Types.INTEGER);
	        if(key.equals(KeyConstants._CURRENTROW)) return new QueryColumnRef(this,key,Types.INTEGER);
	        if(key.equals(KeyConstants._COLUMNLIST)) return new QueryColumnRef(this,key,Types.INTEGER);
		}
        throw new DatabaseException("key ["+key.getString()+"] not found in query, columns are ["+getColumnlist(false)+"]",null,sql,null);
	}
	

	private void renameEL(Collection.Key src, Collection.Key trg) {
		int index=getIndexFromKey(src);
		if(index!=-1){
			columnNames[index]=trg;
			columns[index].setKey(trg);
		}
	}
	
	public synchronized void rename(Collection.Key columnName,Collection.Key newColumnName) throws ExpressionException {
		int index=getIndexFromKey(columnName);
		if(index==-1) throw new ExpressionException("invalid column name definitions");
		columnNames[index]=newColumnName;
		columns[index].setKey(newColumnName);
	}
	

	@Override
	public QueryColumn getColumn(String key, QueryColumn defaultValue) {
		return getColumn(KeyImpl.init(key.trim()),defaultValue);
	}

	@Override
	public QueryColumn getColumn(Collection.Key key, QueryColumn defaultValue) {
        int index=getIndexFromKey(key);
		if(index!=-1) return columns[index];
        if(key.length()>=10) {
        	if(key.equals(KeyConstants._RECORDCOUNT)) return new QueryColumnRef(this,key,Types.INTEGER);
	        if(key.equals(KeyConstants._CURRENTROW)) return new QueryColumnRef(this,key,Types.INTEGER);
	        if(key.equals(KeyConstants._COLUMNLIST)) return new QueryColumnRef(this,key,Types.INTEGER);
        }
        return defaultValue;
	}
	
	@Override
	public String toString() {
		Collection.Key[] keys=keys();
		
		StringBuffer sb=new StringBuffer();

		sb.append("Query\n");
		sb.append("---------------------------------------------------\n");
		
		if(sql!=null) {
			sb.append(sql+"\n");
			sb.append("---------------------------------------------------\n");
		}

		if(exeTime>0)	{
			sb.append("Execution Time (ns): "+exeTime+"\n");
			sb.append("---------------------------------------------------\n");
		}
		
		sb.append("Recordcount: "+getRecordcount()+"\n");
		sb.append("---------------------------------------------------\n");
		String trenner="";
		for(int i=0;i<keys.length;i++) {
			trenner+="+---------------------";
		}
		trenner+="+\n";
		sb.append(trenner);
	
	// Header
		for(int i=0;i<keys.length;i++) {
			sb.append(getToStringField(keys[i].getString()));
		}
		sb.append("|\n");
		sb.append(trenner);
		sb.append(trenner);
		
	// body
		for(int i=0;i<recordcount;i++) {
			for(int y=0;y<keys.length;y++) {
				try {
					Object o=getAt(keys[y],i+1);
					if(o instanceof String)sb.append(getToStringField(o.toString()));
					else if(o instanceof Number) sb.append(getToStringField(Caster.toString(((Number)o))));
					else if(o instanceof Clob) sb.append(getToStringField(Caster.toString(o)));							
					else sb.append(getToStringField(o.toString()));
				} catch (PageException e) {
					sb.append(getToStringField("[empty]"));
				}
			}
			sb.append("|\n");
			sb.append(trenner);
		}
		return sb.toString();
	}

	private String getToStringField(String str) {
		if(str==null) return "|                    ";
		else if(str.length()<21) {
			String s="|"+str;
			for(int i=str.length();i<21;i++)s+=" ";
			return s;
		}
		else if(str.length()==21) return "|"+str;
		else  return "|"+str.substring(0,18)+"...";
	}
	
	/**
	 * 
	 * @param type
	 * @return return String represetation of a Type from int type
	 */
	public static String getColumTypeName(int type) {
		switch(type) {
			case Types.ARRAY: return "OBJECT";
			case Types.BIGINT: return "BIGINT";
			case Types.BINARY: return "BINARY";
			case Types.BIT: return "BIT";
			case Types.BLOB: return "OBJECT";
			case Types.BOOLEAN: return "BOOLEAN";
			case Types.CHAR: return "CHAR";
			case Types.NCHAR: return "NCHAR";
			case Types.CLOB: return "OBJECT";
			case Types.NCLOB: return "OBJECT";
			case Types.DATALINK: return "OBJECT";
			case Types.DATE: return "DATE";
			case Types.DECIMAL: return "DECIMAL";
			case Types.DISTINCT: return "OBJECT";
			case Types.DOUBLE: return "DOUBLE";
			case Types.FLOAT: return "DOUBLE";
			case Types.INTEGER: return "INTEGER";
			case Types.JAVA_OBJECT: return "OBJECT";
			case Types.LONGVARBINARY: return "LONGVARBINARY";
			case Types.LONGVARCHAR: return "LONGVARCHAR";
			case Types.NULL: return "OBJECT";
			case Types.NUMERIC: return "NUMERIC";
			case Types.OTHER: return "OBJECT";
			case Types.REAL: return "REAL";
			case Types.REF: return "OBJECT";
			case Types.SMALLINT: return "SMALLINT";
			case Types.STRUCT: return "OBJECT";
			case Types.TIME: return "TIME";
			case Types.TIMESTAMP: return "TIMESTAMP";
			case Types.TINYINT: return "TINYINT";
			case Types.VARBINARY: return "VARBINARY";
			case Types.NVARCHAR: return "NVARCHAR";
			case Types.SQLXML: return "SQLXML";
			case Types.VARCHAR: return "VARCHAR";
			default : return "VARCHAR";
		}
	}

	private int getIndexFromKey(String key) {
		String lc = StringUtil.toLowerCase(key);
		for(int i=0;i<columnNames.length;i++) {
			if(columnNames[i].getLowerString().equals(lc)) return i;
		}
		return -1;
	}
	
	private int getIndexFromKey(Collection.Key key) {
		
		for(int i=0;i<columnNames.length;i++) {
			if(columnNames[i].equalsIgnoreCase(key)) return i;
		}
		return -1;
	}
	
	@Override
	public void setExecutionTime(long exeTime) {
		this.exeTime=exeTime;
	}

    /**
     * @param maxrows
     * @return has cutted or not
     */
    public synchronized boolean cutRowsTo(int maxrows) {
        //disconnectCache();
        
        if(maxrows>-1 && maxrows<getRecordcount()) {
			 for(int i=0;i<columns.length;i++) {
			 	QueryColumn column = columns[i];
			 	column.cutRowsTo(maxrows);
			 }
			 recordcount=maxrows;
			 return true;
        }
        return false;
    }

    @Override
    public void setCached(boolean isCached) {
        this.isCached=isCached; 
    }

    @Override
    public boolean isCached() {
        return isCached;
    }



    @Override
    public int addRow() {
		addRow(1);
		return getRecordcount();
    }


    public Key getColumnName(int columnIndex) {
    	Key[] keys = keys();
		if(columnIndex<1 || columnIndex>keys.length) return null;
		return keys[columnIndex-1];
    }

    @Override
    public int getColumnIndex(String coulmnName) {
        Collection.Key[] keys = keys();
		for(int i=0;i<keys.length;i++) {
			if(keys[i].getString().equalsIgnoreCase(coulmnName)) return i+1;
		}
		return -1;
    }



    @Override
    public String[] getColumns() {
        return getColumnNamesAsString();
    }
    
    @Override
    public Collection.Key[] getColumnNames() {
    	Collection.Key[] keys = keys();
    	Collection.Key[] rtn=new Collection.Key[keys.length];
		System.arraycopy(keys,0,rtn,0,keys.length);
		return rtn;
    }
    
    public void setColumnNames(Collection.Key[] trg) throws PageException {
    	columncount=trg.length;
    	Collection.Key[] src = keys();
    	
    	// target < source
    	if(trg.length<src.length){
    		this.columnNames=new Collection.Key[trg.length];
    		QueryColumnImpl[] tmp=new QueryColumnImpl[trg.length];
    		for(int i=0;i<trg.length;i++){
    			this.columnNames[i]=trg[i];
    			tmp[i]=this.columns[i];
    			tmp[i].setKey(trg[i]);
        	}
    		this.columns=tmp;
    		return;
    	}
    	
    	if(trg.length>src.length){
    		int recordcount=getRecordcount();
    		for(int i=src.length;i<trg.length;i++){
    			
    			Array arr=new ArrayImpl();
    			for(int r=1;r<=recordcount;r++){
    				arr.setE(i,"");
    			}
    			addColumn(trg[i], arr);
    		}
    		src = keys();
    	}
    	
		for(int i=0;i<trg.length;i++){
			this.columnNames[i]=trg[i];
			this.columns[i].setKey(trg[i]);
    	}
    }
    


	@Override
	public String[] getColumnNamesAsString() {
		return CollectionUtil.keysAsString(this);
	}
	
	public int getColumnCount() {
		return columncount;
	}

    @Override
    public String getData(int row, int col) throws IndexOutOfBoundsException {
        Collection.Key[] keys = keys();
		if(col<1 || col>keys.length) {
			new IndexOutOfBoundsException("invalid column index to retrieve Data from query, valid index goes from 1 to "+keys.length);
		}
		
		Object o=getAt(keys[col-1],row,NullSupportHelper.NULL());
		if(o==NullSupportHelper.NULL())
			throw new IndexOutOfBoundsException("invalid row index to retrieve Data from query, valid index goes from 1 to "+getRecordcount());
		return Caster.toString( o,NullSupportHelper.full()?null:"");
    }



    @Override
    public String getName() {
        return this.name;
    }



    @Override
    public int getRowCount() {
        return getRecordcount();
    }



    @Override
    public void setData(int row, int col, String value) throws IndexOutOfBoundsException {
        Collection.Key[] keys = keys();
		if(col<1 || col>keys.length) {
			new IndexOutOfBoundsException("invalid column index to retrieve Data from query, valid index goes from 1 to "+keys.length);
		}
		try {
			setAt(keys[col-1],row,value);
		} 
		catch (PageException e) {
			throw new IndexOutOfBoundsException("invalid row index to retrieve Data from query, valid index goes from 1 to "+getRecordcount());
		}
    }

    @Override
    public boolean containsKey(String key) {
        return getColumn(key,null)!=null;
    }	

	@Override
	public boolean containsKey(Collection.Key key) {
        return getColumn(key,null)!=null;
	}
	
    @Override
    public String castToString() throws ExpressionException {
        throw new ExpressionException("Can't cast Complex Object Type Query to String",
          "Use Built-In-Function \"serialize(Query):String\" to create a String from Query");
    }

	@Override
	public String castToString(String defaultValue) {
		return defaultValue;
	}


    @Override
    public boolean castToBooleanValue() throws ExpressionException {
        throw new ExpressionException("Can't cast Complex Object Type Query to a boolean value");
    }
    
    @Override
    public Boolean castToBoolean(Boolean defaultValue) {
        return defaultValue;
    }


    @Override
    public double castToDoubleValue() throws ExpressionException {
        throw new ExpressionException("Can't cast Complex Object Type Query to a number value");
    }
    
    @Override
    public double castToDoubleValue(double defaultValue) {
        return defaultValue;
    }


    @Override
    public DateTime castToDateTime() throws ExpressionException {
        throw new ExpressionException("Can't cast Complex Object Type Query to a Date");
    }
    
    @Override
    public DateTime castToDateTime(DateTime defaultValue) {
        return defaultValue;
    }

	@Override
	public int compareTo(boolean b) throws ExpressionException {
		throw new ExpressionException("can't compare Complex Object Type Query with a boolean value");
	}

	@Override
	public int compareTo(DateTime dt) throws PageException {
		throw new ExpressionException("can't compare Complex Object Type Query with a DateTime Object");
	}

	@Override
	public int compareTo(double d) throws PageException {
		throw new ExpressionException("can't compare Complex Object Type Query with a numeric value");
	}

	@Override
	public int compareTo(String str) throws PageException {
		throw new ExpressionException("can't compare Complex Object Type Query with a String");
	}

    public synchronized Array getMetaDataSimple() {
    	Array cols=new ArrayImpl();
    	Struct column;
        for(int i=0;i<columns.length;i++) {
        	column=new StructImpl();
        	column.setEL(KeyConstants._name,columnNames[i].getString());
        	column.setEL("isCaseSensitive",Boolean.FALSE);
        	column.setEL("typeName",columns[i].getTypeAsString());
        	cols.appendEL(column);
        }
        return cols;
    }

	/**
	 * @return the sql
	 */
	public SQL getSql() {
		return sql;
	}

	/**
	 * @param sql the sql to set
	 */
	public void setSql(SQL sql) {
		this.sql = sql;
	}


	@Override
	public Object getObject(String columnName) throws SQLException {
		int currentrow;
		if((currentrow=arrCurrentRow.get(getPid(),0))==0) return null;
		return getAt(columnName,currentrow,null);
	}
	
	@Override
	public Object getObject(int columnIndex) throws SQLException {
		if(columnIndex>0 && columnIndex<=columncount) return  getObject(this.columnNames[columnIndex-1].getString());
		return null;
	}
	
	@Override
	public String getString(int columnIndex) throws SQLException {
		Object rtn = getObject(columnIndex);
		if(rtn==null)return null;
		if(Decision.isCastableToString(rtn)) return Caster.toString(rtn,null);
		throw new SQLException("can't cast value to string");
	}
	
	@Override
	public String getString(String columnName) throws SQLException {
		Object rtn = getObject(columnName);
		if(rtn==null)return null;
		if(Decision.isCastableToString(rtn)) return Caster.toString(rtn,null);
		throw new SQLException("can't cast value to string");
	}
	
	@Override
	public boolean getBoolean(int columnIndex) throws SQLException {
		Object rtn = getObject(columnIndex);
		if(rtn==null)return false;
		if(Decision.isCastableToBoolean(rtn)) return Caster.toBooleanValue(rtn,false);
		throw new SQLException("can't cast value to boolean");
	}
	
	@Override
	public boolean getBoolean(String columnName) throws SQLException {
		Object rtn = getObject(columnName);
		if(rtn==null)return false;
		if(Decision.isCastableToBoolean(rtn)) return Caster.toBooleanValue(rtn,false);
		throw new SQLException("can't cast value to boolean");
	}
	
	
	// ---------------------------------------

	@Override
	public Object call(PageContext pc, Key methodName, Object[] arguments) throws PageException {
		return MemberUtil.call(pc, this, methodName, arguments, railo.commons.lang.CFTypes.TYPE_QUERY, "query");
		//return Reflector.callMethod(this,methodName,arguments);
	}

	@Override
	public Object callWithNamedValues(PageContext pc, Key methodName, Struct args) throws PageException {	
		return MemberUtil.callWithNamedValues(pc, this, methodName, args,railo.commons.lang.CFTypes.TYPE_QUERY, "query");
	}

	@Override
	public Object get(PageContext pc, Key key, Object defaultValue) {
		return getAt(key,arrCurrentRow.get(
				pc.getId(),1),defaultValue);
	}

	@Override
	public Object get(PageContext pc, Key key) throws PageException {
		return getAt(key,arrCurrentRow.get(pc.getId(),1));
	}

	public boolean isInitalized() {
		return true;
	}

	@Override
	public Object set(PageContext pc, Key propertyName, Object value) throws PageException {
		return setAt(propertyName,arrCurrentRow.get(pc.getId(),1),value);
	}

	@Override
	public Object setEL(PageContext pc, Key propertyName, Object value) {
		return setAtEL(propertyName,arrCurrentRow.get(pc.getId(),1),value);
	}
	
	@Override
	public boolean wasNull() {
		throw new PageRuntimeException(new ApplicationException("method [wasNull] is not supported"));
	}

	@Override
	public boolean absolute(int row) throws SQLException {
		if(recordcount==0) {
			if(row!=0) throw new SQLException("invalid row ["+row+"], query is Empty");
			return false;
		}
		//row=row%recordcount;
		
		if(row>0) arrCurrentRow.set(getPid(),row);
		else arrCurrentRow.set(getPid(),(recordcount+1)+row);
		return true;
	}

	@Override
	public void afterLast() throws SQLException {
		arrCurrentRow.set(getPid(),recordcount+1);
	}

	@Override
	public void beforeFirst() throws SQLException {
		arrCurrentRow.set(getPid(),0);
	}

	@Override
	public void cancelRowUpdates() throws SQLException {
		// ignored
	}

	@Override
	public void clearWarnings() throws SQLException {
		// ignored
	}

	@Override
	public void close() throws SQLException {
		// ignored
	}

	@Override
	public void deleteRow() throws SQLException {
		try {
			removeRow(arrCurrentRow.get(getPid()));
		} catch (Exception e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public int findColumn(String columnName) throws SQLException {
		int index= getColumnIndex(columnName);
		if(index==-1) throw new SQLException("invald column definitions ["+columnName+"]");
		return index;
	}

	@Override
	public boolean first() throws SQLException {
		return absolute(1);
	}

	public java.sql.Array getArray(int i) throws SQLException {
		throw new SQLException("method is not implemented");
	}

	public java.sql.Array getArray(String colName) throws SQLException {
		throw new SQLException("method is not implemented");
	}

	@Override
	public InputStream getAsciiStream(int columnIndex) throws SQLException {
		String res = getString(columnIndex);
		if(res==null)return null;
		return new ByteArrayInputStream(res.getBytes());
	}

	@Override
	public InputStream getAsciiStream(String columnName) throws SQLException {
		String res = getString(columnName);
		if(res==null)return null;
		return new ByteArrayInputStream(res.getBytes());
	}

	@Override
	public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
		return new BigDecimal(getDouble(columnIndex));
	}

	@Override
	public BigDecimal getBigDecimal(String columnName) throws SQLException {
		return new BigDecimal(getDouble(columnName));
	}

	@Override
	public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {
		return new BigDecimal(getDouble(columnIndex));
	}

	@Override
	public BigDecimal getBigDecimal(String columnName, int scale) throws SQLException {
		return new BigDecimal(getDouble(columnName));
	}

	@Override
	public InputStream getBinaryStream(int columnIndex) throws SQLException {
		Object obj = getObject(columnIndex);
		if(obj==null)return null;
		try {
			return Caster.toInputStream(obj,(Charset)null);
		} catch (Exception e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public InputStream getBinaryStream(String columnName) throws SQLException {
		Object obj = getObject(columnName);
		if(obj==null)return null;
		try {
			return Caster.toInputStream(obj,(Charset)null);
		} catch (Exception e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public Blob getBlob(int i) throws SQLException {
		byte[] bytes = getBytes(i);
		if(bytes==null) return null;
		try {
			return BlobImpl.toBlob(bytes);
		} 
		catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public Blob getBlob(String colName) throws SQLException {
		byte[] bytes = getBytes(colName);
		if(bytes==null) return null;
		try {
			return BlobImpl.toBlob(bytes);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public byte getByte(int columnIndex) throws SQLException {
		Object obj = getObject(columnIndex);
		if(obj==null) return (byte)0;
		try {
			return Caster.toByteValue(obj);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public byte getByte(String columnName) throws SQLException {
		Object obj = getObject(columnName);
		if(obj==null) return (byte)0;
		try {
			return Caster.toByteValue(obj);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public byte[] getBytes(int columnIndex) throws SQLException {
		Object obj = getObject(columnIndex);
		if(obj==null) return null;
		try {
			return Caster.toBytes(obj,(Charset)null);
		} catch (Exception e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public byte[] getBytes(String columnName) throws SQLException {
		Object obj = getObject(columnName);
		if(obj==null) return null;
		try {
			return Caster.toBytes(obj,(Charset)null);
		} catch (Exception e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public Reader getCharacterStream(int columnIndex) throws SQLException {
		String str=getString(columnIndex);
		if(str==null) return null;
		return new StringReader(str);
	}

	@Override
	public Reader getCharacterStream(String columnName) throws SQLException {
		String str=getString(columnName);
		if(str==null) return null;
		return new StringReader(str);
	}

	@Override
	public Clob getClob(int i) throws SQLException {
		String str=getString(i);
		if(str==null) return null;
		return ClobImpl.toClob(str);
	}

	@Override
	public Clob getClob(String colName) throws SQLException {
		String str=getString(colName);
		if(str==null) return null;
		return ClobImpl.toClob(str);
	}

	@Override
	public int getConcurrency() throws SQLException {
		return 0;
	}

	@Override
	public String getCursorName() throws SQLException {
		return null;
	}

	@Override
	public java.sql.Date getDate(int columnIndex) throws SQLException {
		Object obj=getObject(columnIndex);
		if(obj==null) return null;
		try {
			return new java.sql.Date(Caster.toDate(obj, false, null).getTime());
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public java.sql.Date getDate(String columnName) throws SQLException {
		Object obj=getObject(columnName);
		if(obj==null) return null;
		try {
			return new java.sql.Date(Caster.toDate(obj, false, null).getTime());
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public java.sql.Date getDate(int columnIndex, Calendar cal)throws SQLException {
		return getDate(columnIndex); // TODO impl
	}

	@Override
	public java.sql.Date getDate(String columnName, Calendar cal) throws SQLException {
		return getDate(columnName);// TODO impl
	}

	@Override
	public double getDouble(int columnIndex) throws SQLException {
		Object obj=getObject(columnIndex);
		if(obj==null) return 0;
		try {
			return Caster.toDoubleValue(obj);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public double getDouble(String columnName) throws SQLException {
		Object obj=getObject(columnName);
		if(obj==null) return 0;
		try {
			return Caster.toDoubleValue(obj);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public int getFetchDirection() throws SQLException {
		return 1000;
	}

	@Override
	public int getFetchSize() throws SQLException {
		return 0;
	}

	@Override
	public float getFloat(int columnIndex) throws SQLException {
		Object obj=getObject(columnIndex);
		if(obj==null) return 0;
		try {
			return Caster.toFloatValue(obj);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public float getFloat(String columnName) throws SQLException {
		Object obj=getObject(columnName);
		if(obj==null) return 0;
		try {
			return Caster.toFloatValue(obj);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public int getInt(int columnIndex) throws SQLException {
		Object obj=getObject(columnIndex);
		if(obj==null) return 0;
		try {
			return Caster.toIntValue(obj);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public int getInt(String columnName) throws SQLException {
		Object obj=getObject(columnName);
		if(obj==null) return 0;
		try {
			return Caster.toIntValue(obj);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public long getLong(int columnIndex) throws SQLException {
		Object obj=getObject(columnIndex);
		if(obj==null) return 0;
		try {
			return Caster.toLongValue(obj);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public long getLong(String columnName) throws SQLException {
		Object obj=getObject(columnName);
		if(obj==null) return 0;
		try {
			return Caster.toLongValue(obj);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public Object getObject(int i, Map map) throws SQLException {
		throw new SQLException("method is not implemented");
	}

	@Override
	public Object getObject(String colName, Map map) throws SQLException {
		throw new SQLException("method is not implemented");
	}

	// used only with java 7, do not set @Override
	public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
		return (T) QueryUtil.getObject(this,columnIndex, type);
	}

	// used only with java 7, do not set @Override
	public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {
		return (T) QueryUtil.getObject(this,columnLabel, type);
	}

	@Override
	public Ref getRef(int i) throws SQLException {
		throw new SQLException("method is not implemented");
	}

	@Override
	public Ref getRef(String colName) throws SQLException {
		throw new SQLException("method is not implemented");
	}

	@Override
	public int getRow() throws SQLException {
		return arrCurrentRow.get(getPid(),0);
	}

	@Override
	public short getShort(int columnIndex) throws SQLException {
		Object obj=getObject(columnIndex);
		if(obj==null) return 0;
		try {
			return Caster.toShortValue(obj);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public short getShort(String columnName) throws SQLException {
		Object obj=getObject(columnName);
		if(obj==null) return 0;
		try {
			return Caster.toShortValue(obj);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	public Statement getStatement() throws SQLException {
		throw new SQLException("method is not implemented");
	}

	@Override
	public Time getTime(int columnIndex) throws SQLException {
		Object obj=getObject(columnIndex);
		if(obj==null) return null;
		try {
			return new Time(DateCaster.toTime(null, obj).getTime());
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public Time getTime(String columnName) throws SQLException {
		Object obj=getObject(columnName);
		if(obj==null) return null;
		try {
			return new Time(DateCaster.toTime(null, obj).getTime());
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public Time getTime(int columnIndex, Calendar cal) throws SQLException {
		return getTime(columnIndex);// TODO impl
	}

	@Override
	public Time getTime(String columnName, Calendar cal) throws SQLException {
		return getTime(columnName);// TODO impl
	}

	@Override
	public Timestamp getTimestamp(int columnIndex) throws SQLException {
		Object obj=getObject(columnIndex);
		if(obj==null) return null;
		try {
			return new Timestamp(DateCaster.toTime(null, obj).getTime());
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public Timestamp getTimestamp(String columnName) throws SQLException {
		Object obj=getObject(columnName);
		if(obj==null) return null;
		try {
			return new Timestamp(DateCaster.toTime(null, obj).getTime());
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {
		return getTimestamp(columnIndex);// TODO impl
	}

	@Override
	public Timestamp getTimestamp(String columnName, Calendar cal) throws SQLException {
		return getTimestamp(columnName);// TODO impl
	}

	@Override
	public int getType() throws SQLException {
		return 0;
	}

	@Override
	public URL getURL(int columnIndex) throws SQLException {
		throw new SQLException("method is not implemented");
	}

	@Override
	public URL getURL(String columnName) throws SQLException {
		throw new SQLException("method is not implemented");
	}

	@Override
	public InputStream getUnicodeStream(int columnIndex) throws SQLException {
		String str=getString(columnIndex);
		if(str==null) return null;
		try {
			return new ByteArrayInputStream(str.getBytes("UTF-8"));
		} 
		catch (UnsupportedEncodingException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public InputStream getUnicodeStream(String columnName) throws SQLException {
		String str=getString(columnName);
		if(str==null) return null;
		try {
			return new ByteArrayInputStream(str.getBytes("UTF-8"));
		} 
		catch (UnsupportedEncodingException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public SQLWarning getWarnings() throws SQLException {
		throw new SQLException("method is not implemented");
	}

	@Override
	public void insertRow() throws SQLException {
		throw new SQLException("method is not implemented");
	}

	@Override
	public boolean isAfterLast() throws SQLException {
		return getCurrentrow(ThreadLocalPageContext.get().getId())>recordcount;
	}

	@Override
	public boolean isBeforeFirst() throws SQLException {
		return arrCurrentRow.get(getPid(),0)==0;
	}

	@Override
	public boolean isFirst() throws SQLException {
		return arrCurrentRow.get(getPid(),0)==1;
	}

	public boolean isLast() throws SQLException {
		return arrCurrentRow.get(getPid(),0)==recordcount;
	}

	public boolean last() throws SQLException {
		return absolute(recordcount);
	}

	public void moveToCurrentRow() throws SQLException {
		// ignore
	}

	public void moveToInsertRow() throws SQLException {
		// ignore
	}


	public boolean previous() {
		return previous(getPid());
	}
	
	public boolean previous(int pid) {
		if(0<(arrCurrentRow.set(pid,arrCurrentRow.get(pid,0)-1))) {
			return true;
		}
		arrCurrentRow.set(pid,0);
		return false;
	}

	public void refreshRow() throws SQLException {
		// ignore
		
	}

	@Override
	public boolean relative(int rows) throws SQLException {
		return absolute(getRow()+rows);
	}

	@Override
	public boolean rowDeleted() throws SQLException {
		return false;
	}

	@Override
	public boolean rowInserted() throws SQLException {
		return false;
	}

	@Override
	public boolean rowUpdated() throws SQLException {
		return false;
	}

	public void setFetchDirection(int direction) throws SQLException {
		// ignore
	}

	public void setFetchSize(int rows) throws SQLException {
		// ignore
	}

	@Override
	public void updateArray(int columnIndex, java.sql.Array x)throws SQLException {
		updateObject(columnIndex, x.getArray());
	}

	@Override
	public void updateArray(String columnName, java.sql.Array x)throws SQLException {
		updateObject(columnName, x.getArray());
	}

	@Override
	public void updateAsciiStream(int columnIndex, InputStream x, int length)throws SQLException {
		updateBinaryStream(columnIndex, x, length);
	}

	@Override
	public void updateAsciiStream(String columnName, InputStream x, int length)throws SQLException {
		updateBinaryStream(columnName, x, length);
	}

	@Override
	public void updateBigDecimal(int columnIndex, BigDecimal x)throws SQLException {
		updateObject(columnIndex, x.toString());
	}

	@Override
	public void updateBigDecimal(String columnName, BigDecimal x) throws SQLException {
		updateObject(columnName, x.toString());
	}

	@Override
	public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException {
		try {
			updateObject(columnIndex, IOUtil.toBytesMax(x, length));
		} catch (IOException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public void updateBinaryStream(String columnName, InputStream x, int length) throws SQLException {
		try {
			updateObject(columnName, IOUtil.toBytesMax(x, length));
		} catch (IOException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public void updateBlob(int columnIndex, Blob x) throws SQLException {
		try {
			updateObject(columnIndex, toBytes(x));
		} catch (IOException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public void updateBlob(String columnName, Blob x) throws SQLException {
		try {
			updateObject(columnName, toBytes(x));
		} catch (IOException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public void updateBoolean(int columnIndex, boolean x) throws SQLException {
		updateObject(columnIndex, Caster.toBoolean(x));
	}

	@Override
	public void updateBoolean(String columnName, boolean x) throws SQLException {
		updateObject(columnName, Caster.toBoolean(x));
	}

	@Override
	public void updateByte(int columnIndex, byte x) throws SQLException {
		updateObject(columnIndex, new Byte(x));
	}

	@Override
	public void updateByte(String columnName, byte x) throws SQLException {
		updateObject(columnName, new Byte(x));
	}

	@Override
	public void updateBytes(int columnIndex, byte[] x) throws SQLException {
		updateObject(columnIndex, x);
	}

	@Override
	public void updateBytes(String columnName, byte[] x) throws SQLException {
		updateObject(columnName, x);
	}

	@Override
	public void updateCharacterStream(int columnIndex, Reader reader, int length)throws SQLException {
		try {
			updateObject(columnIndex, IOUtil.toString(reader));
		} catch (Exception e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public void updateCharacterStream(String columnName, Reader reader,int length) throws SQLException {
		try {
			updateObject(columnName, IOUtil.toString(reader));
		} catch (Exception e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public void updateClob(int columnIndex, Clob x) throws SQLException {
		try {
			updateObject(columnIndex, toString(x));
		} catch (IOException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public void updateClob(String columnName, Clob x) throws SQLException {
		try {
			updateObject(columnName, toString(x));
		} catch (IOException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public void updateDate(int columnIndex, java.sql.Date x)throws SQLException {
		updateObject(columnIndex, Caster.toDate(x, false, null, null));
	}

	@Override
	public void updateDate(String columnName, java.sql.Date x)throws SQLException {
		updateObject(columnName, Caster.toDate(x, false, null, null));
	}

	@Override
	public void updateDouble(int columnIndex, double x) throws SQLException {
		updateObject(columnIndex, Caster.toDouble(x));
	}

	@Override
	public void updateDouble(String columnName, double x) throws SQLException {
		updateObject(columnName, Caster.toDouble(x));
	}

	@Override
	public void updateFloat(int columnIndex, float x) throws SQLException {
		updateObject(columnIndex, Caster.toDouble(x));
	}

	@Override
	public void updateFloat(String columnName, float x) throws SQLException {
		updateObject(columnName, Caster.toDouble(x));
	}

	@Override
	public void updateInt(int columnIndex, int x) throws SQLException {
		updateObject(columnIndex, Caster.toDouble(x));
	}

	@Override
	public void updateInt(String columnName, int x) throws SQLException {
		updateObject(columnName, Caster.toDouble(x));
	}

	@Override
	public void updateLong(int columnIndex, long x) throws SQLException {
		updateObject(columnIndex, Caster.toDouble(x));
	}

	@Override
	public void updateLong(String columnName, long x) throws SQLException {
		updateObject(columnName, Caster.toDouble(x));
	}

	@Override
	public void updateNull(int columnIndex) throws SQLException {
		updateObject(columnIndex, null);
	}

	@Override
	public void updateNull(String columnName) throws SQLException {
		updateObject(columnName, null);
	}

	@Override
	public void updateObject(int columnIndex, Object x) throws SQLException {
		try {
			set(getColumnName(columnIndex), x);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public void updateObject(String columnName, Object x) throws SQLException {
		try {
			set(KeyImpl.init(columnName), x);
		} catch (PageException e) {
			throw new SQLException(e.getMessage());
		}
	}

	@Override
	public void updateObject(int columnIndex, Object x, int scale)throws SQLException {
		updateObject(columnIndex, x);
	}

	@Override
	public void updateObject(String columnName, Object x, int scale)throws SQLException {
		updateObject(columnName, x);
	}

	@Override
	public void updateRef(int columnIndex, Ref x) throws SQLException {
		updateObject(columnIndex, x.getObject());
	}

	@Override
	public void updateRef(String columnName, Ref x) throws SQLException {
		updateObject(columnName, x.getObject());
	}

	public void updateRow() throws SQLException {
		throw new SQLException("method is not implemented");
	}

	@Override
	public void updateShort(int columnIndex, short x) throws SQLException {
		updateObject(columnIndex, Caster.toDouble(x));
	}

	@Override
	public void updateShort(String columnName, short x) throws SQLException {
		updateObject(columnName, Caster.toDouble(x));
	}

	@Override
	public void updateString(int columnIndex, String x) throws SQLException {
		updateObject(columnIndex, x);
	}

	@Override
	public void updateString(String columnName, String x) throws SQLException {
		updateObject(columnName, x);
	}

	@Override
	public void updateTime(int columnIndex, Time x) throws SQLException {
		updateObject(columnIndex, new DateTimeImpl(x.getTime(),false));
	}

	@Override
	public void updateTime(String columnName, Time x) throws SQLException {
		updateObject(columnName, new DateTimeImpl(x.getTime(),false));
	}

	@Override
	public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException {
		updateObject(columnIndex, new DateTimeImpl(x.getTime(),false));
	}

	@Override
	public void updateTimestamp(String columnName, Timestamp x) throws SQLException {
		updateObject(columnName, new DateTimeImpl(x.getTime(),false));
	}

	@Override
	public ResultSetMetaData getMetaData() throws SQLException {
		throw new SQLException("method is not implemented");
	}

	@Override
	public Iterator<Collection.Key> keyIterator() {
		return new KeyIterator(keys());
	}
    
	@Override
	public Iterator<String> keysAsStringIterator() {
    	return new StringIterator(keys());
    }
	
	@Override
	public Iterator<Entry<Key, Object>> entryIterator() {
		return new EntryIterator(this, keys());
	}
	
	public Iterator<Object> valueIterator() {
		return new CollectionIterator(keys(),this);
	}

	public void readExternal(ObjectInput in) throws IOException {
		try {
			QueryImpl other=(QueryImpl) new CFMLExpressionInterpreter().interpret(ThreadLocalPageContext.get(),in.readUTF());
			this.arrCurrentRow=other.arrCurrentRow;
			this.columncount=other.columncount;
			this.columnNames=other.columnNames;
			this.columns=other.columns;
			this.exeTime=other.exeTime;
			this.generatedKeys=other.generatedKeys;
			this.isCached=other.isCached;
			this.name=other.name;
			this.recordcount=other.recordcount;
			this.sql=other.sql;
			this.updateCount=other.updateCount;
			
		} catch (PageException e) {
			throw new IOException(e.getMessage());
		}
	}

	public void writeExternal(ObjectOutput out) {
		try {
			out.writeUTF(new ScriptConverter().serialize(this));
		} 
		catch (Throwable t) {}
	}

	public int getHoldability() throws SQLException {
		throw notSupported();
	}

	public boolean isClosed() throws SQLException {
		return false;
	}

	public void updateNString(int columnIndex, String nString)throws SQLException {
		updateString(columnIndex, nString);
	}

	public void updateNString(String columnLabel, String nString)throws SQLException {
		updateString(columnLabel, nString);
	}

	

	public String getNString(int columnIndex) throws SQLException {
		return getString(columnIndex);
	}

	public String getNString(String columnLabel) throws SQLException {
		return getString(columnLabel);
	}

	public Reader getNCharacterStream(int columnIndex) throws SQLException {
		return getCharacterStream(columnIndex);
	}

	public Reader getNCharacterStream(String columnLabel) throws SQLException {
		return getCharacterStream(columnLabel);
	}

	public void updateNCharacterStream(int columnIndex, Reader x, long length)throws SQLException {
		updateCharacterStream(columnIndex, x, length);
	}

	public void updateNCharacterStream(String columnLabel, Reader reader,long length) throws SQLException {
		throw notSupported();
	}

	public void updateAsciiStream(int columnIndex, InputStream x, long length)throws SQLException {
		throw notSupported();
	}

	public void updateBinaryStream(int columnIndex, InputStream x, long length)throws SQLException {
		throw notSupported();
	}

	public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
		throw notSupported();
	}

	public void updateAsciiStream(String columnLabel, InputStream x, long length)throws SQLException {
		throw notSupported();
	}

	public void updateBinaryStream(String columnLabel, InputStream x,long length) throws SQLException {
		throw notSupported();
	}

	public void updateCharacterStream(String columnLabel, Reader reader,long length) throws SQLException {
		throw notSupported();
	}

	public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException {
		throw notSupported();
	}

	public void updateBlob(String columnLabel, InputStream inputStream,long length) throws SQLException {
		throw notSupported();
	}

	public void updateClob(int columnIndex, Reader reader, long length) throws SQLException {
		throw notSupported();
	}

	public void updateClob(String columnLabel, Reader reader, long length) throws SQLException {
		throw notSupported();
	}

	public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException {
		updateClob(columnIndex, reader, length);
	}

	public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException {
		updateClob(columnLabel, reader,length);
	}

	public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException {
		updateCharacterStream(columnIndex, x);
	}

	public void updateNCharacterStream(String columnLabel, Reader reader)throws SQLException {
		throw notSupported();
	}

	public void updateAsciiStream(int columnIndex, InputStream x)throws SQLException {
		throw notSupported();
	}

	public void updateBinaryStream(int columnIndex, InputStream x)throws SQLException {
		throw notSupported();
	}

	public void updateCharacterStream(int columnIndex, Reader x) throws SQLException {
		throw notSupported();
	}

	public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException {
		throw notSupported();
	}

	public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException {
		throw notSupported();
	}

	public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException {
		throw notSupported();
	}

	public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException {
		throw notSupported();
	}

	public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException {
		throw notSupported();
	}

	public void updateClob(int columnIndex, Reader reader) throws SQLException {
		throw notSupported();
	}

	public void updateClob(String columnLabel, Reader reader) throws SQLException {
		throw notSupported();
	}

	public void updateNClob(int columnIndex, Reader reader) throws SQLException {
		updateClob(columnIndex, reader);
	}

	public void updateNClob(String columnLabel, Reader reader) throws SQLException {
		updateClob(columnLabel, reader);
	}

	public <T> T unwrap(Class<T> iface) throws SQLException {
		throw notSupported();
	}

	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		throw notSupported();
	}
	

	
	//JDK6: uncomment this for compiling with JDK6 
	 
	public void updateNClob(int columnIndex, NClob nClob) throws SQLException {
		throw notSupported();
	}

	public void updateNClob(String columnLabel, NClob nClob) throws SQLException {
		throw notSupported();
	}

	public NClob getNClob(int columnIndex) throws SQLException {
		throw notSupported();
	}

	public NClob getNClob(String columnLabel) throws SQLException {
		throw notSupported();
	}

	public SQLXML getSQLXML(int columnIndex) throws SQLException {
		throw notSupported();
	}

	public SQLXML getSQLXML(String columnLabel) throws SQLException {
		throw notSupported();
	}

	public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException {
		throw notSupported();
	}

	public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException {
		throw notSupported();
	}
	
	public RowId getRowId(int columnIndex) throws SQLException {
		throw notSupported();
	}

	public RowId getRowId(String columnLabel) throws SQLException {
		throw notSupported();
	}

	public void updateRowId(int columnIndex, RowId x) throws SQLException {
		throw notSupported();
	}

	public void updateRowId(String columnLabel, RowId x) throws SQLException {
		throw notSupported();
	}
	
	public void removeRows(int index, int count) throws PageException {
		QueryUtil.removeRows(this,index,count);
	}
	

	private SQLException notSupported() {
		return new SQLException("this feature is not supported");
	}

	public synchronized void enableShowQueryUsage() {
		if(columns!=null)for(int i=0;i<columns.length;i++){
			columns[i]=columns[i]._toDebugColumn();
		}
	}

	@Override
	public long getExecutionTime() {
		return exeTime;
	}
	
	public static QueryImpl cloneQuery(Query qry,boolean deepCopy) {
        QueryImpl newResult=new QueryImpl();
        ThreadLocalDuplication.set(qry, newResult);
        try{
	        newResult.columnNames=qry.getColumnNames();
	        newResult.columns=new QueryColumnImpl[newResult.columnNames.length];
	        QueryColumn col;
	        for(int i=0;i<newResult.columnNames.length;i++) {
	        	col =  qry.getColumn(newResult.columnNames[i],null);
	        	newResult.columns[i]=QueryUtil.duplicate2QueryColumnImpl(newResult,col,deepCopy);
	        }
	        
		        
		    newResult.sql=qry.getSql();
	        newResult.template=qry.getTemplate();
	        newResult.recordcount=qry.getRecordcount();
	        newResult.columncount=newResult.columnNames.length;
	        newResult.isCached=qry.isCached();
	        newResult.name=qry.getName();
	        newResult.exeTime=qry.getExecutionTime();
	        newResult.updateCount=qry.getUpdateCount();
	        if(qry.getGeneratedKeys()!=null)newResult.generatedKeys=((QueryImpl)qry.getGeneratedKeys()).cloneQuery(false);
	        return newResult;
        }
        finally {
        	// ThreadLocalDuplication.remove(qry); removed "remove" to catch sisters and brothers
        }
    }
	
	@Override
	public java.util.Iterator getIterator() {
		return new ForEachQueryIterator(this, ThreadLocalPageContext.get().getId());
    }
	
	@Override
	public boolean equals(Object obj){
		if(!(obj instanceof Collection)) return false;
		return CollectionUtil.equals(this,(Collection)obj);
	}
	
	/*@Override
	public int hashCode() {
		return CollectionUtil.hashCode(this);
	}*/
}