package jeql.engine.query.join; import jeql.api.row.BasicRow; import jeql.api.row.Row; import jeql.api.row.RowIterator; import jeql.api.row.RowList; import jeql.api.row.RowSchema; import jeql.engine.query.QueryScope; import jeql.syntax.FromItem; import jeql.syntax.ParseTreeNode; public class NestedLoopJoinRowList implements RowList { private RowSchema schema = null; private RowList[] rowStr = new RowList[2]; private ParseTreeNode joinConditionExpr; private int joinType = FromItem.JOIN_INNER; private QueryScope scope; public NestedLoopJoinRowList(RowList rs0, RowList rs1, ParseTreeNode joinConditionExpr, int joinType, QueryScope scope) { this.joinConditionExpr = joinConditionExpr; this.joinType = joinType; this.scope = scope; rowStr = new RowList[] { rs0, rs1 }; schema = new RowSchema(rs0.getSchema(), rs1.getSchema()); } public RowSchema getSchema() { return schema; } public RowIterator iterator() { NestedLoopJoinRowIterator loopIt = new NestedLoopJoinRowIterator(schema, rowStr, joinType); JoinCondFilterRowIterator joinCondIt = new JoinCondFilterRowIterator(schema, loopIt, joinConditionExpr, scope); return joinCondIt; } private static class NestedLoopJoinRowIterator implements RowIterator { private RowSchema schema; private RowList[] rowStr; private RowIterator[] rowIt; private Row row0 = null; private Row row1 = null; private boolean isCurrentLeftRowIncluded = false; private boolean isLeftOuterJoin = false; private boolean applyFilter = true; public NestedLoopJoinRowIterator(RowSchema schema, RowList[] rowStr, int joinType) { if (joinType == FromItem.JOIN_LEFT_OUTER) isLeftOuterJoin = true; this.schema = schema; this.rowStr = rowStr; rowIt = new RowIterator[rowStr.length]; rowIt[0] = rowStr[0].iterator(); rowIt[1] = rowStr[1].iterator(); } public RowSchema getSchema() { return schema; } public Row next() { // initialize loops state if (joinRow == null) { // just started // get first rows in each stream row0 = rowIt[0].next(); if (row0 == null) return null; row1 = rowIt[1].next(); if (row1 == null) { if (isLeftOuterJoin && ! isCurrentLeftRowIncluded) { applyFilter = false; isCurrentLeftRowIncluded = true; return createRow(row0, null); } return null; } return createRow(row0, row1); } // here can assume that both tables are non-empty (otherwise would have returned above) // inner loop (second table) row1 = rowIt[1].next(); if (row1 != null) { return createRow(row0, row1); } // outer loop (first (driving) table) // if current left row is not yet included and this is a LEFT OUTER JOIN, include it if (isLeftOuterJoin && ! isCurrentLeftRowIncluded) { applyFilter = false; isCurrentLeftRowIncluded = true; return createRow(row0, null); } row0 = rowIt[0].next(); isCurrentLeftRowIncluded = false; applyFilter = true; if (row0 != null) { rowIt[1] = rowStr[1].iterator(); row1 = rowIt[1].next(); return createRow(row0, row1); } return null; } public boolean applyFilter() { if (! applyFilter) { applyFilter = true; return false; } return true; } public void rowAccepted() { isCurrentLeftRowIncluded = true; } private BasicRow joinRow = null; private Row createRow(Row row0, Row row1) { joinRow = new BasicRow(schema.size()); joinRow.copyTo(0, row0); if (row1 != null) joinRow.copyTo(row0.size(), row1); else joinRow.setColumnsToNull(row0.size(), joinRow.size()); return joinRow; } } private static class JoinCondFilterRowIterator implements RowIterator { private RowSchema schema; private ParseTreeNode condExpr; private QueryScope scope; private NestedLoopJoinRowIterator rowIt; private int rowNum = 0; public JoinCondFilterRowIterator(RowSchema schema, NestedLoopJoinRowIterator rowIt, ParseTreeNode condExpr, QueryScope scope) { this.schema = schema; this.condExpr = condExpr; this.scope = scope; this.rowIt = rowIt; rowNum = 0; } public RowSchema getSchema() { return schema; } public Row next() { // short-circuit if no filter expr if (condExpr == null) { rowNum++; // set rownum for use in where clause expressions scope.setRowNum(rowNum); return rowIt.next(); } // skip to next accepted row (if any) while (true) { rowNum++; // set rownum for use in where clause expressions scope.setRowNum(rowNum); Row row = rowIt.next(); if (row == null) return null; // don't filter rows included for outer join if (! rowIt.applyFilter()) return row; if (isAccepted(row)) { rowIt.rowAccepted(); return row; } } } private boolean isAccepted(Row row) { if (condExpr == null) return true; ((QueryScope) scope).setRow(row); boolean isAccepted = ((Boolean) condExpr.eval(scope)).booleanValue(); return isAccepted; } } }