package jeql.syntax; import java.util.Iterator; import java.util.List; import jeql.api.error.ExecutionException; import jeql.api.error.ImplementationException; import jeql.api.row.ArrayRowList; import jeql.api.row.BasicRow; import jeql.api.row.RowList; import jeql.api.row.RowSchema; import jeql.api.table.Table; import jeql.engine.CompilationException; import jeql.engine.ConfigurationException; import jeql.engine.Scope; import jeql.engine.ScopeUtil; import jeql.engine.TableConstants; /** * A table-valued expression, optionally with an alias * and renamed columns. * * @author Martin Davis * */ public class TableExpressionNode extends ParseTreeNode { private ParseTreeNode expr = null; private String alias = null; private List colNames = null; private int colListLine = 0; public TableExpressionNode(ParseTreeNode expr, String alias, List colNames, int colListLine) { this.expr = expr; this.alias = alias; this.colNames = colNames; this.colListLine = colListLine; setLoc(expr); } public String getAlias() { return alias; } public boolean hasAlias() { return alias != null; } public List getAliasColumnNames() { return colNames; } public ParseTreeNode getExpression() { return expr; } public String getTableName() { if (expr instanceof TableRefNode) { return ((TableRefNode) expr).getTableName(); } return null; } public boolean isSubquery() { return (expr instanceof SelectNode); } public void bind(Scope scope) { // check that subqueries have an alias if (isSubquery()) { if (! hasAlias()) { throw new CompilationException(this, "Subquery must have alias specified"); } } expr.bind(scope); } public Object eval(Scope scope) { // TableExpressions are never evaled throw new UnsupportedOperationException(); } public Class getType(Scope scope) { // TableExpressions do not have a type throw new UnsupportedOperationException(); } public Table getTable(Scope scope) { if (expr instanceof TableRefNode) { return getTableRef(scope); } else if (expr instanceof TableValueNode) { return getTableValue(scope); } else if (expr instanceof FunctionNode) { return getFunctionValue(scope); } else if (expr instanceof SelectNode) { return getSelectValue(scope); } throw new ImplementationException("Unexpected TableExpression node type: " + expr); // return null; } private Table getTableRef(Scope scope) { Table srcTbl = ScopeUtil.resolveTableNonNull(scope, ((TableRefNode) expr).getTableName(), this); RowList rows = (RowList) srcTbl.getRows(); Table tbl = new Table(rows); assignAlias(tbl); return tbl; } private Table getTableValue(Scope scope) { Table tbl = (Table) ((TableValueNode) expr).eval(scope); assignAlias(tbl); return tbl; } private Table getFunctionValue(Scope scope) { Object val = expr.eval(scope); Table tbl = null; if (isTableValued(val)) { tbl = convertCollectionToTable(val); } else { tbl = convertScalarToTable(val); } assignAlias(tbl); return tbl; } private Table getSelectValue(Scope scope) { Table tbl = (Table) expr.eval(scope); // since this table was created by the select, it can be updated with new names assignAlias(tbl); return tbl; } private static Table convertScalarToTable(Object val) { RowSchema schema = new RowSchema(TableConstants.FUNCTION_COL_NAME, val.getClass()); ArrayRowList rs = new ArrayRowList(schema); BasicRow row = new BasicRow(1); row.setValue(0, val); rs.add(row); Table tbl = new Table(rs); return tbl; } private static boolean isTableValued(Object o) { if (o instanceof List) return true; if (o instanceof Table) return true; return false; } private Table convertCollectionToTable(Object o) { if (o instanceof List) { List l = (List) o; Class type = l.get(0).getClass(); RowSchema schema = new RowSchema(TableConstants.FUNCTION_COL_NAME, type); ArrayRowList rs = new ArrayRowList(schema); for (Iterator i = l.iterator(); i.hasNext(); ) { Object val = i.next(); BasicRow row = new BasicRow(1); row.setValue(0, val); rs.add(row); } Table tbl = new Table(rs); return tbl; } else if (o instanceof Table) { return (Table) o; } throw new ConfigurationException("Can't convert to table: " + o.getClass()); } private void assignAlias(Table tbl) { // use alias as table name, if present if (alias != null) tbl.setName(alias); /** * Set column names if they are specified. * Otherwise, table keeps column names from underlying rowlist. */ if (colNames != null) { if (colNames.size() != tbl.size()) throw new CompilationException(colListLine, "Alias column name list length does not match table width"); String[] colNameArray = (String[]) colNames.toArray(new String[0]); tbl.changeColumnNames(colNameArray); } } }