package net.sourceforge.mayfly.evaluation; import net.sourceforge.mayfly.MayflyException; import net.sourceforge.mayfly.MayflyInternalException; import net.sourceforge.mayfly.Options; import net.sourceforge.mayfly.datastore.Cell; import net.sourceforge.mayfly.datastore.Row; import net.sourceforge.mayfly.datastore.StringCell; import net.sourceforge.mayfly.evaluation.expression.SingleColumn; import net.sourceforge.mayfly.evaluation.select.Evaluator; import net.sourceforge.mayfly.parser.Location; import net.sourceforge.mayfly.util.CaseInsensitiveString; import net.sourceforge.mayfly.util.ImmutableList; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; /** * @internal * Mapping from Expression to Cell. * * Unlike a {@link Row}, here we have an ordering. In some contexts * (for example, evaluating an expression), the order doesn't matter, * and in fact might be constructed haphazardly. * * However, when copying data which we'll return in a select, the * order here needs to * match the order of columns in a table (for "select foo.*"), * and even has some user-visible meaning between tables (for "select *"), * although in the latter case I'm not sure what the meaning * needs to be. */ public class ResultRow { private final ImmutableList<Element> elements; public ResultRow(Row row, String table) { this(row, table, new Options()); } public ResultRow(Row row, String table, Options options) { this.elements = fromRow(row, table, options); } public ResultRow() { this(new ImmutableList()); } private ResultRow(ImmutableList elements) { this.elements = elements; } private static ImmutableList<Element> fromRow(Row row, String table, Options options) { List result = new ArrayList(); for (Iterator iter = row.columnNames(); iter.hasNext();) { CaseInsensitiveString column = (CaseInsensitiveString) iter.next(); result.add(new Element( new SingleColumn(table, column.getString(), options), row.cell(column))); } return new ImmutableList(result); } public int size() { return elements.size(); } public Element element(int index) { return elements.get(index); } public Cell cell(int index) { return element(index).value; } public Expression expression(int index) { return element(index).expression; } public Expression findColumn(String columnName) { return findColumn(null, null, columnName, Location.UNKNOWN); } public SingleColumn findColumn( String tableOrAlias, String originalTableOrAlias, String columnName, Location location) { SingleColumn found = findColumnOrNull(tableOrAlias, columnName, location); if (found == null) { throw new NoColumn(originalTableOrAlias, columnName, location); } else { return found; } } public SingleColumn findColumnOrNull( String tableOrAlias, String columnName, Location location) { SingleColumn found = null; for (Element element : elements) { if (element.expression instanceof SingleColumn) { SingleColumn candidate = (SingleColumn) element.expression; if (candidate.matches(tableOrAlias, columnName)) { if (found != null) { throw new MayflyException( "ambiguous column " + columnName, location); } else { found = candidate; } } } } return found; } public Cell findValue(Expression target) { Cell result = findValueOrNull(target); if (result == null) { throw new MayflyInternalException( "Where did expression " + target.displayName() + " come from?"); } else { return result; } } public Cell findValueOrNull(Expression target) { Expression resolved = target.resolve(this, Evaluator.NO_SUBSELECT_NEEDED); for (Element element : elements) { if (resolved.matches(element.expression)) { return element.value; } } return null; } public Cell findOrEvaluate(Expression expression) { Cell result = findValueOrNull(expression); if (result != null) { return result; } else { return expression.evaluate(this); } } public ResultRow withColumn(String tableOrAlias, String columnName, Cell cell) { return with(new SingleColumn(tableOrAlias, columnName), cell); } public ResultRow withColumn(String tableOrAlias, String column, String value) { return withColumn(tableOrAlias, column, new StringCell(value)); } public ResultRow with(Expression expression, Cell value) { return new ResultRow(elements.with(new Element(expression, value))); } public String debugString() { StringBuilder out = new StringBuilder(); out.append("Result Row:\n"); for (Element element : elements) { out.append(" "); out.append(element.expression.displayName()); out.append(" = "); out.append(element.value.displayName()); out.append("\n"); } return out.toString(); } public static class Element { public final Expression expression; public final Cell value; public Element(Expression expression, Cell value) { /* This is probably where we want to go eventually. But we need to work through things like how we convert a Row to a ResultRow (do we even need to do that, outside applyAlias, beyond the transition phase)? There are also issues with group-by keys. if (expression instanceof SingleColumn) { // might also want to recurse to make sure all // sub-expressions are resolved. But this will // do for now. ((SingleColumn)expression).assertIsResolved(); } */ this.expression = expression; this.value = value; } public SingleColumn column() { return (SingleColumn)expression; } } /** * Return a new row which has all the columns from two input * rows. */ public ResultRow combine(ResultRow right) { Set leftTableNames = new HashSet(); List result = new ArrayList(); Iterator leftIterator = elements.iterator(); while (leftIterator.hasNext()) { Element element = (Element) leftIterator.next(); leftTableNames.add(new CaseInsensitiveString( element.column().tableOrAlias())); result.add(element); } Iterator rightIterator = right.elements.iterator(); while (rightIterator.hasNext()) { Element element = (Element) rightIterator.next(); String tableOrAlias = element.column().tableOrAlias(); if (leftTableNames.contains(new CaseInsensitiveString(tableOrAlias))) { throw new MayflyException( "duplicate table name or alias " + tableOrAlias); } result.add(element); } return new ResultRow(new ImmutableList(result)); } public ImmutableList expressions() { List result = new ArrayList(); for (Iterator iter = elements.iterator(); iter.hasNext();) { Element element = (Element) iter.next(); result.add(element.expression); } return new ImmutableList(result); } public ImmutableList expressionsForTable(String aliasOrTable) { List found = new ArrayList(); for (Element element : elements) { if (element.expression instanceof SingleColumn) { SingleColumn column = (SingleColumn) element.expression; if (column.matchesAliasOrTable(aliasOrTable)) { found.add(column); } } } return new ImmutableList(found); } }