package jeql.syntax; import jeql.api.error.ExecutionException; import jeql.api.table.Table; import jeql.engine.CompilationException; import jeql.engine.Scope; import jeql.engine.UndefinedVariableException; import jeql.engine.query.QueryScope; import jeql.engine.query.group.GroupScope; import jeql.util.StringUtil; /** * * @author Martin Davis * @version 1.0 */ public class TableColumnNode extends ParseTreeNode { private String table = null; private String col = null; private boolean isColumn = false; private int colIndex = -1; /** * A correlated column references * the value of a column in one * of the tables in a FROM clause */ private boolean isCorrelatedCol = false; private Class correlatedVarType = null; private Object correlatedVarValue = null; private boolean checkUndefinedVariables = true; /** * Models <tt>column</tt> specifier. * * @param table */ public TableColumnNode(String col) { this.col = col; table = null; } /** * Models <tt>tbl.column</tt> syntax. * * @param table */ public TableColumnNode(String table, String col) { this.table = table; this.col = col; } public void setCheckUndefinedVariables(boolean checkUndefined) { this.checkUndefinedVariables = checkUndefined; } /** * Gets the specified table name, if present. * * @return the table name if present * @return null if no table name was specified */ public String getTableName() { return table; } public String getColName() { return col; } public boolean isMatch(String tblName, String colName) { return StringUtil.isEqualOrBothNull(tblName, table) && col.equals(colName); } private boolean isColumn() { if (colIndex >= 0) return true; if (correlatedVarType != null) return true; return false; } public void bind(Scope scope) { // reset state in case this is a node in an inner select correlatedVarType = null; correlatedVarValue = null; isColumn = false; colIndex = -1; if (scope instanceof QueryScope) { colIndex = ((QueryScope) scope).getColumnIndex(table, col); } if (colIndex >= 0) { isColumn = true; return; } // check for correlated column if (scope instanceof QueryScope) { QueryScope colScope = findColumnScope((QueryScope) scope, table, col); // if not a column in current scope, check if it is a correlated variable from a parent query scope if (colScope != null) { findCorrelatedValue((QueryScope) scope, table, col); if (isColumn()) { isColumn = true; return; } } } // only need this check if variables are required to be defined before use if (! isColumn && checkUndefinedVariables) { if (table != null) { String msg = "Table " + table + " is not defined (in correlated variable " + table + "." + col + ")"; Table t = null; try { t = ((QueryScope) scope).resolveTable(table); } catch (UndefinedVariableException e) { throw new ExecutionException(this, msg); } if (t != null) { msg = "Column '" + col + "' is not defined in table " + table; } throw new ExecutionException(this, msg); } if (! scope.hasVariable(col)) { String scopeDesc = ""; if (scope instanceof GroupScope) scopeDesc = " in GROUP scope"; String msg = "Variable '" + col + "' is not defined" + scopeDesc; throw new ExecutionException(this, msg); } } } /* public void OLDbind(Scope scope) { //correlatedValue = null; if (scope instanceof QueryScope) { colIndex = ((QueryScope) scope).getColumnIndex(table, col); } isColumn = colIndex >= 0; // only need this check if variables are required to be defined before use if (! isColumn) { if (! scope.hasVariable(col)) { String scopeDesc = ""; if (scope instanceof GroupScope) scopeDesc = " in GROUP scope"; String msg = "Variable " + col + " is not defined" + scopeDesc; throw new CompilationException(this, msg); } } } */ /** * * @param table * @param col * @return the value of the correlated column, if any * @return null if this column is not defined in any parent query scope */ private QueryScope findColumnScope(Scope scope, String tblName, String col) { while (scope instanceof QueryScope) { QueryScope qs = (QueryScope) scope; int colIndex = qs.getColumnIndex(tblName, col); if (colIndex >= 0) { return qs; } scope = scope.getParent(); } return null; } /** * Finds this column in a parent QueryScope (if any), * and if found records the value and type. * If found this column is a correlated column, * and the value and type are fixed for the * evaluation of the query in which this column node occurs * * @param table * @param col * @return the value of the correlated column, if any * @return null if this column is not defined in any parent query scope */ private void findCorrelatedValue(QueryScope scope, String tblName, String col) { Scope parentScope = scope.getParent(); while (parentScope instanceof QueryScope) { QueryScope qs = (QueryScope) parentScope; int colIndex = qs.getColumnIndex(tblName, col); if (colIndex >= 0) { isCorrelatedCol = true; correlatedVarType = qs.getColumnType(colIndex); correlatedVarValue = qs.getColumnValue(colIndex); } parentScope = parentScope.getParent(); } } public Class getType(Scope scope) { if (isColumn) { if (colIndex >= 0) return ((QueryScope) scope).getColumnType(colIndex); return correlatedVarType; } if (scope instanceof QueryScope) { QueryScope queryScope = (QueryScope) scope; if (queryScope.hasVariable(col)) { return queryScope.getVariableType(col); } } // for vars which have been computed outside SELECT, can get type of value Object varVal = scope.getVariable(col); if (varVal == null) return null; return varVal.getClass(); } public Object eval(Scope scope) { if (isColumn) { if (colIndex >= 0) return ((QueryScope) scope).getRow().getValue(colIndex); else return correlatedVarValue; } return scope.getVariable(col); } }