package railo.runtime.tag;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import railo.commons.io.IOUtil;
import railo.commons.lang.StringUtil;
import railo.commons.sql.SQLUtil;
import railo.runtime.PageContext;
import railo.runtime.config.Constants;
import railo.runtime.db.DataSource;
import railo.runtime.db.DataSourceManager;
import railo.runtime.db.DatasourceConnection;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.DatabaseException;
import railo.runtime.exp.PageException;
import railo.runtime.ext.tag.TagImpl;
import railo.runtime.listener.ApplicationContextPro;
import railo.runtime.op.Caster;
import railo.runtime.timer.Stopwatch;
import railo.runtime.type.Array;
import railo.runtime.type.ArrayImpl;
import railo.runtime.type.Collection;
import railo.runtime.type.Collection.Key;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.Query;
import railo.runtime.type.QueryColumn;
import railo.runtime.type.QueryImpl;
import railo.runtime.type.SVArray;
import railo.runtime.type.Struct;
import railo.runtime.type.StructImpl;
import railo.runtime.type.util.KeyConstants;

/**
* Handles all interactions with files. The attributes you use with cffile depend on the value of the action attribute. 
*  For example, if the action = "write", use the attributes associated with writing a text file.
*
*
*
**/
public final class DBInfo extends TagImpl {

	private static final Key TABLE_NAME = KeyImpl.intern("TABLE_NAME");
	private static final Key COLUMN_NAME = KeyImpl.intern("COLUMN_NAME");
	private static final Key IS_PRIMARYKEY = KeyImpl.intern("IS_PRIMARYKEY");
	private static final Key IS_FOREIGNKEY = KeyImpl.intern("IS_FOREIGNKEY");
	private static final Key COLUMN_DEF = KeyImpl.intern("COLUMN_DEF");
	private static final Key COLUMN_DEFAULT_VALUE = KeyImpl.intern("COLUMN_DEFAULT_VALUE");
	private static final Key COLUMN_DEFAULT = KeyImpl.intern("COLUMN_DEFAULT");
	private static final Key REFERENCED_PRIMARYKEY = KeyImpl.intern("REFERENCED_PRIMARYKEY");
	private static final Key REFERENCED_PRIMARYKEY_TABLE = KeyImpl.intern("REFERENCED_PRIMARYKEY_TABLE");
	private static final Key USER = KeyImpl.intern("USER");
	private static final Key TABLE_SCHEM = KeyImpl.intern("TABLE_SCHEM");
	private static final Key DECIMAL_DIGITS = KeyImpl.intern("DECIMAL_DIGITS");

	private static final Key DATABASE_NAME = KeyImpl.intern("database_name");
	private static final Key TABLE_CAT = KeyImpl.intern("TABLE_CAT");
	private static final Key PROCEDURE = KeyImpl.intern("procedure");
	private static final Key CATALOG = KeyImpl.intern("catalog");
	private static final Key SCHEMA = KeyImpl.intern("schema");
	private static final Key DATABASE_PRODUCTNAME = KeyImpl.intern("DATABASE_PRODUCTNAME");
	private static final Key DATABASE_VERSION = KeyImpl.intern("DATABASE_VERSION");
	private static final Key DRIVER_NAME = KeyImpl.intern("DRIVER_NAME");
	private static final Key DRIVER_VERSION = KeyImpl.intern("DRIVER_VERSION");
	private static final Key JDBC_MAJOR_VERSION = KeyImpl.intern("JDBC_MAJOR_VERSION");
	private static final Key JDBC_MINOR_VERSION = KeyImpl.intern("JDBC_MINOR_VERSION");	
	
	private static final int TYPE_NONE=0;
	private static final int TYPE_DBNAMES=1;
	private static final int TYPE_TABLES=2;
	private static final int TYPE_TABLE_COLUMNS = 3;
	private static final int TYPE_VERSION = 4;
	private static final int TYPE_PROCEDURES = 5;
	private static final int TYPE_PROCEDURE_COLUMNS = 6;
	private static final int TYPE_FOREIGNKEYS = 7;
	private static final int TYPE_INDEX = 8;
	private static final int TYPE_USERS = 9;
	private static final int TYPE_TERMS = 10;
	private static final Collection.Key CARDINALITY = KeyImpl.init("CARDINALITY");

	
	//private static final String[] ALL_TABLE_TYPES = {"TABLE", "VIEW", "SYSTEM TABLE", "SYNONYM"};
	
	private String datasource;
	private String name;
	private int type;
	
	
	private String dbname;
	private String password;
	private String pattern;
	private String table;
	private String procedure;
	private String username;
	private String strType;
	
	
	@Override
	public void release()	{
		super.release();
		datasource=null;
		name=null;
		type=TYPE_NONE;
		dbname=null;
		password=null;
		pattern=null;
		table=null;
		procedure=null;
		username=null;
		
		
		
	}

	/**
	 * @param procedure the procedure to set
	 */
	public void setProcedure(String procedure) {
		this.procedure = procedure;
	}

	/**
	 * @param datasource the datasource to set
	 */
	public void setDatasource(String datasource) {
		this.datasource = datasource;
	}

	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @param type the type to set
	 * @throws ApplicationException 
	 */
	public void setType(String strType) throws ApplicationException {
		this.strType=strType;
		strType=strType.toLowerCase().trim();
		
		if("dbnames".equals(strType)) 			this.type=TYPE_DBNAMES;
		else if("dbname".equals(strType)) 		this.type=TYPE_DBNAMES;
		else if("tables".equals(strType)) 		this.type=TYPE_TABLES;
		else if("table".equals(strType)) 		this.type=TYPE_TABLES;
		else if("columns".equals(strType)) 		this.type=TYPE_TABLE_COLUMNS;
		else if("column".equals(strType)) 		this.type=TYPE_TABLE_COLUMNS;
		else if("version".equals(strType)) 		this.type=TYPE_VERSION;
		else if("procedures".equals(strType)) 	this.type=TYPE_PROCEDURES;
		else if("procedure".equals(strType)) 	this.type=TYPE_PROCEDURES;
		

		else if("table_columns".equals(strType))	this.type=TYPE_TABLE_COLUMNS;
		else if("table_column".equals(strType))		this.type=TYPE_TABLE_COLUMNS;
		else if("column_table".equals(strType))		this.type=TYPE_TABLE_COLUMNS;
		else if("column_tables".equals(strType))	this.type=TYPE_TABLE_COLUMNS;
		
		else if("tablecolumns".equals(strType))		this.type=TYPE_TABLE_COLUMNS;
		else if("tablecolumn".equals(strType))		this.type=TYPE_TABLE_COLUMNS;
		else if("columntable".equals(strType))		this.type=TYPE_TABLE_COLUMNS;
		else if("columntables".equals(strType))		this.type=TYPE_TABLE_COLUMNS;
		
		else if("procedure_columns".equals(strType))	this.type=TYPE_PROCEDURE_COLUMNS;
		else if("procedure_column".equals(strType))		this.type=TYPE_PROCEDURE_COLUMNS;
		else if("column_procedure".equals(strType))		this.type=TYPE_PROCEDURE_COLUMNS;
		else if("column_procedures".equals(strType))	this.type=TYPE_PROCEDURE_COLUMNS;
		
		else if("procedurecolumns".equals(strType))	this.type=TYPE_PROCEDURE_COLUMNS;
		else if("procedurecolumn".equals(strType))		this.type=TYPE_PROCEDURE_COLUMNS;
		else if("columnprocedure".equals(strType))		this.type=TYPE_PROCEDURE_COLUMNS;
		else if("columnprocedures".equals(strType))	this.type=TYPE_PROCEDURE_COLUMNS;
		
		else if("foreignkeys".equals(strType)) 	this.type=TYPE_FOREIGNKEYS;
		else if("foreignkey".equals(strType)) 	this.type=TYPE_FOREIGNKEYS;
		else if("index".equals(strType)) 		this.type=TYPE_INDEX;
		else if("users".equals(strType)) 		this.type=TYPE_USERS;
		else if("user".equals(strType)) 		this.type=TYPE_USERS;
		
		else if("term".equals(strType)) 	this.type=TYPE_TERMS;
		else if("terms".equals(strType)) 	this.type=TYPE_TERMS;
		
		else throw new ApplicationException("invalid value for attribute type ["+strType+"]",
				"valid values are [dbname,tables,columns,version,procedures,foreignkeys,index,users]");
		
	}

	/**
	 * @param dbname the dbname to set
	 */
	public void setDbname(String dbname) {
		this.dbname = dbname;
	}
	public void setDbnames(String dbname) {
		this.dbname = dbname;
	}

	/**
	 * @param password the password to set
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * @param pattern the pattern to set
	 */
	public void setPattern(String pattern) {
		this.pattern = pattern;
	}

	/**
	 * @param table the table to set
	 */
	public void setTable(String table) {
		this.table = table;
	}

	/**
	 * @param username the username to set
	 */
	public void setUsername(String username) {
		this.username = username;
	}


	@Override
	public int doStartTag() throws PageException	{
		Object ds=getDatasource(pageContext, datasource);
		DataSourceManager manager = pageContext.getDataSourceManager();
		DatasourceConnection dc=ds instanceof DataSource?
	    		manager.getConnection(pageContext,(DataSource)ds,username,password):
	    		manager.getConnection(pageContext,Caster.toString(ds),username,password);
		try {
			
			if(type==TYPE_TABLE_COLUMNS)	typeColumns(dc.getConnection().getMetaData());
			else if(type==TYPE_DBNAMES)		typeDBNames(dc.getConnection().getMetaData());
			else if(type==TYPE_FOREIGNKEYS)	typeForeignKeys(dc.getConnection().getMetaData());
			else if(type==TYPE_INDEX)		typeIndex(dc.getConnection().getMetaData());
			else if(type==TYPE_PROCEDURES)	typeProcedures(dc.getConnection().getMetaData());
			else if(type==TYPE_PROCEDURE_COLUMNS)typeProcedureColumns(dc.getConnection().getMetaData());
			else if(type==TYPE_TERMS)		typeTerms(dc.getConnection().getMetaData());
			else if(type==TYPE_TABLES)		typeTables(dc.getConnection().getMetaData());
			else if(type==TYPE_VERSION)		typeVersion(dc.getConnection().getMetaData());
			else if(type==TYPE_USERS)		typeUsers(dc.getConnection().getMetaData());
			
		}
		catch(SQLException sqle) {
			throw new DatabaseException(sqle,dc);
		}
		finally {
			manager.releaseConnection(pageContext,dc);
		}
		
		
								
			 
		return SKIP_BODY;
	}

	
	
	private void typeColumns(DatabaseMetaData metaData) throws PageException, SQLException {
		required("table",table);
		
		Stopwatch stopwatch=new Stopwatch(Stopwatch.UNIT_NANO);
		stopwatch.start();

		table=setCase(metaData, table);
		pattern=setCase(metaData, pattern);
		if(StringUtil.isEmpty(pattern,true)) pattern=null;
		String schema=null;
		int index=table.indexOf('.');
		if(index>0) {
			schema=table.substring(0,index);
			table=table.substring(index+1);
		}
		checkTable(metaData);
		
        Query qry = new QueryImpl(
        		metaData.getColumns(dbname, schema, table, pattern),
        		"query",
        		pageContext.getTimeZone());
        
		int len=qry.getRecordcount();

		if(qry.getColumn(COLUMN_DEF,null) != null)
			qry.rename(COLUMN_DEF,COLUMN_DEFAULT_VALUE);
		else if(qry.getColumn(COLUMN_DEFAULT,null) != null)
			qry.rename(COLUMN_DEFAULT,COLUMN_DEFAULT_VALUE);
		
		// make sure decimal digits exists
		QueryColumn col = qry.getColumn(DECIMAL_DIGITS,null);
		if(col==null){
			Array arr=new ArrayImpl();
			for(int i=1;i<=len;i++) {
				arr.append(railo.runtime.op.Constants.DOUBLE_ZERO);
			}
			qry.addColumn(DECIMAL_DIGITS, arr);
		}
		
		
		// add is primary
		Map primaries = new HashMap();
		String tblName;
		Array isPrimary=new ArrayImpl();
		Set set;
		Object o;
		for(int i=1;i<=len;i++) {
			
			// decimal digits
			o=qry.getAt(DECIMAL_DIGITS, i,null);
			if(o==null)qry.setAtEL(DECIMAL_DIGITS, i,railo.runtime.op.Constants.DOUBLE_ZERO);
			
			set=(Set) primaries.get(tblName=(String) qry.getAt(TABLE_NAME, i));
			if(set==null) {
				set=toSet(metaData.getPrimaryKeys(dbname, null, tblName),true,"COLUMN_NAME");
				primaries.put(tblName,set);
			}
			isPrimary.append(set.contains(qry.getAt(COLUMN_NAME, i))?"YES":"NO"); 
		}
		qry.addColumn(IS_PRIMARYKEY, isPrimary);
		
		// add is foreignkey
		Map foreigns = new HashMap();
		Array isForeign=new ArrayImpl();
		Array refPrim=new ArrayImpl();
		Array refPrimTbl=new ArrayImpl();
		//Map map,inner;
		Map<String, Map<String, SVArray>> map;
		Map<String, SVArray> inner;
		for(int i=1;i<=len;i++) {
			map=(Map) foreigns.get(tblName=(String) qry.getAt(TABLE_NAME, i));
			if(map==null) {
				map=toMap(
						metaData.getImportedKeys(dbname, schema, table),
						true,
						"FKCOLUMN_NAME",
						new String[]{"PKCOLUMN_NAME","PKTABLE_NAME"});
				foreigns.put(tblName, map);
			}
			inner = map.get(qry.getAt(COLUMN_NAME, i));
			if(inner!=null) {
				isForeign.append("YES");
				refPrim.append(inner.get("PKCOLUMN_NAME")); 
				refPrimTbl.append(inner.get("PKTABLE_NAME"));
			}
			else {
				isForeign.append("NO"); 
				refPrim.append("N/A"); 
				refPrimTbl.append("N/A");   	 
			}
		}
		qry.addColumn(IS_FOREIGNKEY, isForeign);
		qry.addColumn(REFERENCED_PRIMARYKEY, refPrim);
		qry.addColumn(REFERENCED_PRIMARYKEY_TABLE, refPrimTbl);
		
		
		qry.setExecutionTime(stopwatch.time());
        
        
		pageContext.setVariable(name, qry);
	}

	private Map<String,Map<String, SVArray>> toMap(ResultSet result,boolean closeResult, String columnName,String[] additional) throws SQLException {
		Map<String,Map<String, SVArray>> map=new HashMap<String,Map<String, SVArray>>();
		Map<String, SVArray> inner;
		String col;
		SVArray item;
		if(result==null) return map;
		try {
			while(result.next()){
				col=result.getString(columnName);
				inner=map.get(col);
				if(inner!=null) {
					for(int i=0;i<additional.length;i++) {
						item=inner.get(additional[i]);
						item.add(result.getString(additional[i]));
						item.setPosition(item.size());
					}
				}
				else {
					inner=new HashMap<String, SVArray>();
					map.put(col, inner);
					for(int i=0;i<additional.length;i++) {
						item=new SVArray();
						item.add(result.getString(additional[i]));
						inner.put(additional[i], item);
					}
				}
			}
		}
		finally {
			if(closeResult)IOUtil.closeEL(result);
		}
		return map;
	}
	
	private Set<String> toSet(ResultSet result, boolean closeResult, String columnName) throws SQLException {
		Set<String> set = new HashSet<String>();
		if(result==null) return set;
		
		try{
			while(result.next()){
				set.add(result.getString(columnName)); 
			}
			return set;
		}
		finally {
			if(closeResult)IOUtil.closeEL(result);
		}
		
	}

	private void typeDBNames(DatabaseMetaData metaData) throws PageException, SQLException {

		Stopwatch stopwatch=new Stopwatch(Stopwatch.UNIT_NANO);
		stopwatch.start();
        
        railo.runtime.type.Query catalogs = new QueryImpl(metaData.getCatalogs(),"query",pageContext.getTimeZone());
        railo.runtime.type.Query scheme = new QueryImpl(metaData.getSchemas(),"query",pageContext.getTimeZone());
        
        Pattern p=null;
        if(pattern!=null && !"%".equals(pattern)) 
        	p=SQLUtil.pattern(pattern, true);
		
        
        
        String[] columns=new String[]{"database_name","type"};
		String[] types=new String[]{"VARCHAR","VARCHAR"};
		railo.runtime.type.Query qry=new QueryImpl(columns,types,0,"query");
		int row=1,len=catalogs.getRecordcount();
		String value;
		// catalog
		for(int i=1;i<=len;i++) {
			value=(String) catalogs.getAt(TABLE_CAT, i);
			if(!matchPattern(value,p)) continue;
			qry.addRow();
			qry.setAt(DATABASE_NAME, row, value);
			qry.setAt(KeyConstants._type, row, "CATALOG");
			row++;
		}
		// scheme
		len=scheme.getRecordcount();
		for(int i=1;i<=len;i++) {
			value=(String) scheme.getAt(TABLE_SCHEM, i);
			if(!matchPattern(value,p)) continue;
			qry.addRow();
			qry.setAt(DATABASE_NAME, row, value);
			qry.setAt(KeyConstants._type, row, "SCHEMA");
			row++;
		}
		
        qry.setExecutionTime(stopwatch.time());
        
        
		pageContext.setVariable(name, qry);
	}

	private void typeForeignKeys(DatabaseMetaData metaData) throws PageException, SQLException {
		required("table",table);
		
		Stopwatch stopwatch=new Stopwatch(Stopwatch.UNIT_NANO);
		stopwatch.start();
        table=setCase(metaData, table);
        int index=table.indexOf('.');
		String schema=null;
        if(index>0) {
			schema=table.substring(0,index);
			table=table.substring(index+1);
		}
		
		checkTable(metaData);
		
		railo.runtime.type.Query qry = new QueryImpl(
				metaData.getExportedKeys(dbname, schema, table),
				"query",
				pageContext.getTimeZone());		
		qry.setExecutionTime(stopwatch.time());        
        
		pageContext.setVariable(name, qry);
	}

	private void checkTable(DatabaseMetaData metaData) throws SQLException, ApplicationException {
		ResultSet tables =null;
		
		try {
			tables = metaData.getTables(null, null, setCase(metaData,table), null);
			if(!tables.next()) throw new ApplicationException("there is no table that match the following pattern ["+table+"]");
		}
		finally {
			if(tables!=null) tables.close();
		}
	}

	private String setCase(DatabaseMetaData metaData, String id) throws SQLException {
		if(id==null) return null;
		
		if(metaData.storesLowerCaseIdentifiers()) return id.toLowerCase();
		if(metaData.storesUpperCaseIdentifiers()) return id.toUpperCase();
		return id;
	}

	private void typeIndex(DatabaseMetaData metaData) throws PageException, SQLException {
		required("table",table);
		
		Stopwatch stopwatch=new Stopwatch(Stopwatch.UNIT_NANO);
		stopwatch.start();
        
		table=setCase(metaData, table);
        int index=table.indexOf('.');
		String schema=null;
        if(index>0) {
			schema=table.substring(0,index);
			table=table.substring(index+1);
		}
		
		checkTable(metaData);
		
        ResultSet tables = metaData.getIndexInfo(dbname, schema, table, false, true);
        railo.runtime.type.Query qry = new QueryImpl(tables,"query",pageContext.getTimeZone());
        
        // type int 2 string
        int rows = qry.getRecordcount();
        String strType;
        int type,card;
        for(int row=1;row<=rows;row++){
        	
        	// type
        	switch(type=Caster.toIntValue(qry.getAt(KeyConstants._type,row))){
        	case 0:
        		strType="Table Statistic";
        	break;
        	case 1:
	    		strType="Clustered Index";
	    	break;
        	case 2:
	    		strType="Hashed Index";
	    	break;
        	case 3:
	    		strType="Other Index";
	    	break;
	    	default:
	    		strType=Caster.toString(type);
	    	}
        	qry.setAt(KeyConstants._type, row, strType);
        	
        	// CARDINALITY
        	card=Caster.toIntValue(qry.getAt(CARDINALITY,row),0);
        	qry.setAt(CARDINALITY, row, Caster.toDouble(card));
        	
        }
        qry.setExecutionTime(stopwatch.time());
        
        
		pageContext.setVariable(name, qry);
	}

	private void typeProcedures(DatabaseMetaData metaData) throws SQLException, PageException {
		Stopwatch stopwatch=new Stopwatch(Stopwatch.UNIT_NANO);
		stopwatch.start();
        
		String schema=null;
		pattern=setCase(metaData, pattern);
		if(StringUtil.isEmpty(pattern,true)) {
			pattern=null;
		}
		
        railo.runtime.type.Query qry = new QueryImpl(
        		metaData.getProcedures(dbname, schema, pattern),
        		"query",
        		pageContext.getTimeZone());
        qry.setExecutionTime(stopwatch.time());
        
        
		pageContext.setVariable(name, qry);
	}
	
	private void typeProcedureColumns(DatabaseMetaData metaData) throws SQLException, PageException {
		required("procedure",procedure);
		
		Stopwatch stopwatch=new Stopwatch(Stopwatch.UNIT_NANO);
		stopwatch.start();
		
		procedure=setCase(metaData, procedure);		
		pattern=setCase(metaData, pattern);
		if(StringUtil.isEmpty(pattern,true)) pattern=null;
		String schema=null;
		int index=procedure.indexOf('.');
		if(index>0) {
			schema=procedure.substring(0,index);
			procedure=procedure.substring(index+1);
		}
		
		
		railo.runtime.type.Query qry = new QueryImpl(
				metaData.getProcedureColumns(dbname, schema, procedure, pattern),
				"query",
				pageContext.getTimeZone());
		qry.setExecutionTime(stopwatch.time());
        
        
		pageContext.setVariable(name, qry);
	}

	private void typeTerms(DatabaseMetaData metaData) throws SQLException, PageException {
		Struct sct=new StructImpl();
		sct.setEL(PROCEDURE, metaData.getProcedureTerm());
		sct.setEL(CATALOG, metaData.getCatalogTerm());
		sct.setEL(SCHEMA, metaData.getSchemaTerm());
		
		pageContext.setVariable(name, sct);
	}

	private void typeTables(DatabaseMetaData metaData) throws PageException, SQLException {

		
		
		Stopwatch stopwatch=new Stopwatch(Stopwatch.UNIT_NANO);
		stopwatch.start();
        
		pattern=setCase(metaData, pattern);
		
        railo.runtime.type.Query qry = new QueryImpl(
        		metaData.getTables(dbname, null, pattern, null),
        		"query",
        		pageContext.getTimeZone());
        qry.setExecutionTime(stopwatch.time());
        
        
		pageContext.setVariable(name, qry);
	}

	private void typeVersion(DatabaseMetaData metaData) throws PageException, SQLException {

		Stopwatch stopwatch=new Stopwatch(Stopwatch.UNIT_NANO);
		stopwatch.start();
		
		Key[] columns=new Key[]{DATABASE_PRODUCTNAME,DATABASE_VERSION,DRIVER_NAME,DRIVER_VERSION,JDBC_MAJOR_VERSION,JDBC_MINOR_VERSION};
		String[] types=new String[]{"VARCHAR","VARCHAR","VARCHAR","VARCHAR","DOUBLE","DOUBLE"};
		
		railo.runtime.type.Query qry=new QueryImpl(columns,types,1,"query");

		qry.setAt(DATABASE_PRODUCTNAME,1,metaData.getDatabaseProductName());
		qry.setAt(DATABASE_VERSION,1,metaData.getDatabaseProductVersion());
		qry.setAt(DRIVER_NAME,1,metaData.getDriverName());
		qry.setAt(DRIVER_VERSION,1,metaData.getDriverVersion());
		qry.setAt(JDBC_MAJOR_VERSION,1,new Double(metaData.getJDBCMajorVersion()));
		qry.setAt(JDBC_MINOR_VERSION,1,new Double(metaData.getJDBCMinorVersion()));
		
		
        qry.setExecutionTime(stopwatch.time());
        
        
		pageContext.setVariable(name, qry);
	}
	
	private void typeUsers(DatabaseMetaData metaData) throws PageException, SQLException {
		
		Stopwatch stopwatch=new Stopwatch(Stopwatch.UNIT_NANO);
		stopwatch.start();
        
		checkTable(metaData);
		ResultSet result = metaData.getSchemas();
		Query qry = new QueryImpl(result,"query",pageContext.getTimeZone());
		
		
		qry.rename(TABLE_SCHEM,USER);
        
        qry.setExecutionTime(stopwatch.time());
        
        
		pageContext.setVariable(name, qry);
	}

	private void required(String name, String value) throws ApplicationException {
		if(value==null)
			throw new ApplicationException("Missing attribute ["+name+"]. The type ["+strType+"] requires the attribute [" + name + "].");
	}

	private static  boolean matchPattern(String value, Pattern pattern) {
		if(pattern==null) return true;
		return SQLUtil.match(pattern, value);
	}

	@Override
	public int doEndTag()	{
		return EVAL_PAGE;
	}
	

	public static Object getDatasource(PageContext pageContext, String datasource) throws ApplicationException {
		if(StringUtil.isEmpty(datasource)){
			Object ds=((ApplicationContextPro)pageContext.getApplicationContext()).getDefDataSource();

			if(StringUtil.isEmpty(ds))
				throw new ApplicationException(
						"attribute [datasource] is required, when no default datasource is defined",
						"you can define a default datasource as attribute [defaultdatasource] of the tag "+Constants.CFAPP_NAME+" or as data member of the "+Constants.APP_CFC+" (this.defaultdatasource=\"mydatasource\";)");
			return ds;
		}
		return datasource;
	}
}