using System; using System.Collections; using BCG.Utilities; namespace BCG { /// /// Stream to internally handle Left Joins. The only difference between this and the left Join is the waty the /// DataPacket thats fired is initialized. /// [DataStreamUserRespresentation (false)] public class LeftInternalJoinStream : InternalJoinStream { protected DataPacket myLeftDataPacket, myRightDataPacket; //Container to contain all the left rows that are orphaned during a deletion operation private IDictionary myLeftOrphanedRows; public LeftInternalJoinStream (ViewStream vsLeft, string jcLeft, ViewStream vsRight, string jcRight) : base (vsLeft, jcLeft, vsRight, jcRight) {} protected override void myOnFirstSubscriber() { myLeftOperand.master = this; myRightOperand.master = this; //the events are not hooked onto the operands delegate, but rather a level of indirection //added in the form on myLeftChangeHandler myLeftStream.onChange += myLeftChangeHandler; myRightStream.onChange += myRightChangeHandler; } protected override void myOnLastSubscriber () { myInit (); myLeftStream.onChange -= myLeftChangeHandler; myRightStream.onChange -= myRightChangeHandler; } protected override void myInit() { base.myInit (); myLeftOrphanedRows = null; myLeftDataPacket = myRightDataPacket = null; } private void myLeftChangeHandler (object o) { myLeftDataPacket = (DataPacket)o; myTestAndFire (true); } private void myRightChangeHandler (object o) { myRightDataPacket = (DataPacket)o; myTestAndFire (false); } /// /// enforces the order of firing the events, this is to enforce that first the right operand /// has data (possibly 0 rows) before we the join is actually calculated => Performance boost /// /// private void myTestAndFire (bool isLeft) { JoinOperand jo = isLeft ? myLeftOperand : myRightOperand; DataPacket dp = isLeft ? myLeftDataPacket : myRightDataPacket; //to test whether both have received call backs if (myLeftOperand.hasReceivedData) jo.changeHandler (dp); else if (myLeftDataPacket != null && myRightDataPacket != null) { myRightOperand.changeHandler (myRightDataPacket); myLeftOperand.changeHandler (myLeftDataPacket); } } /// /// Called by JoinOperand /// /// /// public override void addRows (IBlueRowCollection rows, bool isLeft) { foreach (IBlueRow row in rows) myAddRow (row, isLeft); if (!isLeft) foreach (RowNode node in myLeftOrphanedRows.Values) ((JoinTable)myLastFiredPacket.newRows).addRow (node.row, null, myLastFiredPacket); } /// /// called by JoinOperand /// /// /// public override void deleteRows (IBlueRowCollection rows, bool isLeft) { myLeftOrphanedRows = new Hashtable (); foreach (IBlueRow row in rows) myDeleteRow (row, isLeft); } private void myAddRow (IBlueRow row, bool isLeft) { if (isLeft) { myLeftOperand.addRow (row); RowList matchedRows = myRightOperand.getMatchingRows (row[myLeftJoinColOrdinal]); if (matchedRows.Count == 0) ((JoinTable)myLastFiredPacket.newRows).addRow (row, null, myLastFiredPacket); else foreach (RowNode matchedNode in matchedRows) ((JoinTable)myLastFiredPacket.newRows).addRow (row, matchedNode.row, myLastFiredPacket); } else { RowList matchedRows = myLeftOperand.getMatchingRows (row[myRightJoinColOrdinal]); int rightMatchingRowCount = myRightOperand.getMatchingRows (row[myRightJoinColOrdinal]).Count; myRightOperand.addRow (row); int newRightMatchingRowCount = myRightOperand.getMatchingRows (row[myRightJoinColOrdinal]).Count; if (matchedRows.Count != 0){ foreach (RowNode matchedNode in matchedRows) { //if the left rows were orphaned in this transaction (ie in this callback), they should //just be removed from the orphanedlist and we dont need to worry about the consistency of //the jointable as a new row will replace it if (rightMatchingRowCount == 0 && newRightMatchingRowCount > 0) { if (myLeftOrphanedRows[matchedNode.row["id"]] == null) ((JoinTable)myLastFiredPacket.newRows).deleteRow (true, matchedNode.row, myLastFiredPacket); else myLeftOrphanedRows.Remove (matchedNode.row["id"]); } //they were orphaned by a previous transaction and hence the orphans need to be //removed from the Jointable and the complete row added ((JoinTable)myLastFiredPacket.newRows).addRow (matchedNode.row, row, myLastFiredPacket); } } } } private void myDeleteRow (IBlueRow row, bool isLeft) { if (isLeft) { myLeftOperand.deleteRow (row); ((JoinTable)myLastFiredPacket.newRows).deleteRow (true, row, myLastFiredPacket); } else { RowList matchedRows = myLeftOperand.getMatchingRows (row[myRightJoinColOrdinal]); int rightMatchingRowCount = myRightOperand.getMatchingRows (row[myRightJoinColOrdinal]).Count; myRightOperand.deleteRow (row); int newRightMatchingRowCount = myRightOperand.getMatchingRows (row[myRightJoinColOrdinal]).Count; if (matchedRows.Count != 0) { if (rightMatchingRowCount > 0 && newRightMatchingRowCount == 0) //ie all the matchedRows were joined with 'this' right row (method arg), and hence they need to //be handled specially, ie joined with nulls, if there are no right rows with this //joincolumn value being inserted. foreach (RowNode matchedNode in matchedRows) { // BlueEventLog.logAssert (myLeftOrphanedRows[matchedNode.row["id"]] == null); myLeftOrphanedRows[matchedNode.row["id"]] = matchedNode; } ((JoinTable)myLastFiredPacket.newRows).deleteRow (false, row, myLastFiredPacket); } } } public override void onTableClear (bool isLeft) { myLastFiredPacket.deletedRows = myLastFiredPacket.newRows; myLastFiredPacket.newRows = new JoinTable (mySchema); myLastFiredPacket.addedRows = new BlueTable (mySchema); if (!isLeft) foreach (IBlueRow row in myLeftDataPacket.newRows) ((JoinTable)myLastFiredPacket.newRows).addRow (row, null, myLastFiredPacket); } } }