List:Commits« Previous MessageNext Message »
From:Ole John Aske Date:June 21 2010 9:41am
Subject:bzr push into mysql-5.1-telco-7.0-spj branch (ole.john.aske:3172 to 3178)
View as plain text  
 3178 Ole John Aske	2010-06-21
      Changed the c'tor ::ndb_table_access_map(table_map) to not depend
      on Bitmap<>::set_map(table_map) which is still not available 
      in the main 5.1 branch.

    modified:
      sql/ha_ndbcluster.cc
 3177 Ole John Aske	2010-06-21
      Removed AQP::get_referred_table_access() as it has become obsolete after latest rewrites
      to ha_ndbcluster.cc() 

    modified:
      sql/abstract_query_plan.cc
      sql/abstract_query_plan.h
 3176 Ole John Aske	2010-06-21
      Integrated join refering multiple parents as pushable joins.
      
      In order to implement the new pushability detection, and still keep the code maintainable
      and extensible for the future, class ndb_pushed_builder_ctx has been introduced in ha_ndbcluster.cc.
      This class contains the context and helper methods used during pushability detection. The previous
      'static field_ref_is_join_pushable()' has now become a member of this class.
      
      ::field_ref_is_join_pushable() has been heavily rewritten in order to implement the new multiparent 
      logic.
      
      New MTR testcases has been added for multiparent testing.

    modified:
      mysql-test/suite/ndb/r/ndb_join_pushdown.result
      mysql-test/suite/ndb/t/ndb_join_pushdown.test
      sql/ha_ndbcluster.cc
 3175 Ole John Aske	2010-06-16
      Updated my latest commit according to Jonas comments: 
      Set Request::RT_ROW_BUFFER directly in ::expand where 
      we already set TreeNode::T_ROW_BUFFER.

    modified:
      storage/ndb/src/kernel/blocks/dbspj/DbspjMain.cpp
 3174 Ole John Aske	2010-06-16
      Extended SPJ API to allow linkedValues to also allow 
      grandparent referrences. Also enforce the requirements
      that all (grand)parents should be reachable through a single
      common parent. In the serialized code all agrandparent refs are
      prefixed with a QueryPattern::P_PARENT.

    modified:
      storage/ndb/src/ndbapi/NdbQueryBuilder.cpp
 3173 Ole John Aske	2010-06-16
      Fixes and enhancements reuired in SPJ block in order to support multiple (grand)parents:
      
      - Fixed 'of by one' error in Dbspj::setupRowPtr() when setting the 'm_data' pointer.
      - Fixed incorrect usage of get_row_ptr_stack() vs. get_row_ptr_var() (Some inverted logic)
      - Enhanced ::expand() used during parseDA() to correctly request T_ROW_BUFFER and T_ROW_BUFFER_MAP
        if it encounters a P_PARENT in the QueryPattern.
      - Added (empty) handling of P_PARENT in the other ::expand() methods for completenes.
      - Removed temporary 'JONAS_TESTING_ROW_BUFFERING' hooks.

    modified:
      storage/ndb/src/kernel/blocks/dbspj/Dbspj.hpp
      storage/ndb/src/kernel/blocks/dbspj/DbspjMain.cpp
 3172 Ole John Aske	2010-06-15
      Fixed incorrect initialization of handler::pushed_cond.
      
      The member field pushed_cond in class handler was only initialized to NULL in sql_select.cc
      'if (tmp || !cond || tab->type == JT_REF)'. This caused some EXPLAINs to incorrectly report a 
      pushed condition in cases where there actually was no conditions at all.
      
      Also corrected explain results which was affected by this bug and removed a redundant MTR testcase
      which was a direct copy of the preceeding testcase.

    modified:
      mysql-test/suite/ndb/r/ndb_join_pushdown.result
      mysql-test/suite/ndb/t/ndb_join_pushdown.test
      sql/sql_select.cc
=== modified file 'mysql-test/suite/ndb/r/ndb_join_pushdown.result'
--- a/mysql-test/suite/ndb/r/ndb_join_pushdown.result	2010-06-15 12:30:28 +0000
+++ b/mysql-test/suite/ndb/r/ndb_join_pushdown.result	2010-06-21 08:11:42 +0000
@@ -638,6 +638,268 @@ a	b	c	d	a	b	c	d
 1	3	1	2	1	2	5	1
 1	4	2	3	2	3	4	5
 set ndb_join_pushdown=true;
+explain
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t2.c and t3.b = t1.c;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	PRIMARY	NULL	NULL	NULL	16	Parent of 3 pushed join@1
+1	SIMPLE	t2	eq_ref	PRIMARY	PRIMARY	8	test.t1.a,test.t1.b	1	Child of pushed join@1
+1	SIMPLE	t3	eq_ref	PRIMARY	PRIMARY	8	test.t2.c,test.t1.c	1	Child of pushed join@1
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t2.c and t3.b = t1.c;
+a	b	c	d	a	b	c	d	a	b	c	d
+1	1	1	1	1	1	1	1	1	1	1	1
+1	3	1	2	1	3	1	2	1	1	1	1
+1	4	2	3	1	4	2	3	2	2	2	2
+2	1	3	4	2	1	3	4	3	3	3	3
+2	2	2	2	2	2	2	2	2	2	2	2
+2	3	4	5	2	3	4	5	4	4	4	4
+3	1	1	2	3	1	1	2	1	1	1	1
+3	2	2	3	3	2	2	3	2	2	2	2
+3	3	3	3	3	3	3	3	3	3	3	3
+3	4	3	4	3	4	3	4	3	3	3	3
+4	1	4	5	4	1	4	5	4	4	4	4
+4	3	1	2	4	3	1	2	1	1	1	1
+4	4	4	4	4	4	4	4	4	4	4	4
+explain
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t3.c and t4.b = t2.c;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	PRIMARY	NULL	NULL	NULL	16	Parent of 3 pushed join@1
+1	SIMPLE	t2	eq_ref	PRIMARY	PRIMARY	8	test.t1.a,test.t1.b	1	Child of pushed join@1
+1	SIMPLE	t3	eq_ref	PRIMARY	PRIMARY	8	test.t1.c,test.t1.d	1	Child of pushed join@1
+1	SIMPLE	t4	eq_ref	PRIMARY	PRIMARY	8	test.t3.c,test.t2.c	1	
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t3.c and t4.b = t2.c;
+a	b	c	d	a	b	c	d	a	b	c	d	a	b	c	d
+1	1	1	1	1	1	1	1	1	1	1	1	1	1	1	1
+1	4	2	3	1	4	2	3	2	3	4	5	4	2	5	1
+2	1	3	4	2	1	3	4	3	4	3	4	3	3	3	3
+2	2	2	2	2	2	2	2	2	2	2	2	2	2	2	2
+3	2	2	3	3	2	2	3	2	3	4	5	4	2	5	1
+3	3	3	3	3	3	3	3	3	3	3	3	3	3	3	3
+3	4	3	4	3	4	3	4	3	4	3	4	3	3	3	3
+4	4	4	4	4	4	4	4	4	4	4	4	4	4	4	4
+explain
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t2.d
+join t1 as t4 on t4.a = t3.c and t4.b = t2.c;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	PRIMARY	NULL	NULL	NULL	16	Parent of 4 pushed join@1
+1	SIMPLE	t2	eq_ref	PRIMARY	PRIMARY	8	test.t1.a,test.t1.b	1	Child of pushed join@1
+1	SIMPLE	t3	eq_ref	PRIMARY	PRIMARY	8	test.t1.c,test.t2.d	1	Child of pushed join@1
+1	SIMPLE	t4	eq_ref	PRIMARY	PRIMARY	8	test.t3.c,test.t2.c	1	Child of pushed join@1
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t2.d
+join t1 as t4 on t4.a = t3.c and t4.b = t2.c;
+a	b	c	d	a	b	c	d	a	b	c	d	a	b	c	d
+1	1	1	1	1	1	1	1	1	1	1	1	1	1	1	1
+1	4	2	3	1	4	2	3	2	3	4	5	4	2	5	1
+2	1	3	4	2	1	3	4	3	4	3	4	3	3	3	3
+2	2	2	2	2	2	2	2	2	2	2	2	2	2	2	2
+3	2	2	3	3	2	2	3	2	3	4	5	4	2	5	1
+3	3	3	3	3	3	3	3	3	3	3	3	3	3	3	3
+3	4	3	4	3	4	3	4	3	4	3	4	3	3	3	3
+4	4	4	4	4	4	4	4	4	4	4	4	4	4	4	4
+explain
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t2.d
+join t1 as t4 on t4.a = t3.c and t4.b = t1.d;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	PRIMARY	NULL	NULL	NULL	16	Parent of 4 pushed join@1
+1	SIMPLE	t2	eq_ref	PRIMARY	PRIMARY	8	test.t1.a,test.t1.b	1	Child of pushed join@1
+1	SIMPLE	t3	eq_ref	PRIMARY	PRIMARY	8	test.t1.c,test.t2.d	1	Child of pushed join@1
+1	SIMPLE	t4	eq_ref	PRIMARY	PRIMARY	8	test.t3.c,test.t1.d	1	Child of pushed join@1
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t2.d
+join t1 as t4 on t4.a = t3.c and t4.b = t1.d;
+a	b	c	d	a	b	c	d	a	b	c	d	a	b	c	d
+1	1	1	1	1	1	1	1	1	1	1	1	1	1	1	1
+1	4	2	3	1	4	2	3	2	3	4	5	4	3	1	2
+2	1	3	4	2	1	3	4	3	4	3	4	3	4	3	4
+2	2	2	2	2	2	2	2	2	2	2	2	2	2	2	2
+3	2	2	3	3	2	2	3	2	3	4	5	4	3	1	2
+3	3	3	3	3	3	3	3	3	3	3	3	3	3	3	3
+3	4	3	4	3	4	3	4	3	4	3	4	3	4	3	4
+4	4	4	4	4	4	4	4	4	4	4	4	4	4	4	4
+explain
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t3.a and t4.b = t2.c;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	PRIMARY	NULL	NULL	NULL	16	Parent of 4 pushed join@1
+1	SIMPLE	t2	eq_ref	PRIMARY	PRIMARY	8	test.t1.a,test.t1.b	1	Child of pushed join@1
+1	SIMPLE	t3	eq_ref	PRIMARY	PRIMARY	8	test.t1.c,test.t1.d	1	Child of pushed join@1
+1	SIMPLE	t4	eq_ref	PRIMARY	PRIMARY	8	test.t1.c,test.t2.c	1	Child of pushed join@1
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t3.a and t4.b = t2.c;
+a	b	c	d	a	b	c	d	a	b	c	d	a	b	c	d
+1	1	1	1	1	1	1	1	1	1	1	1	1	1	1	1
+1	3	1	2	1	3	1	2	1	2	5	1	1	1	1	1
+1	4	2	3	1	4	2	3	2	3	4	5	2	2	2	2
+2	1	3	4	2	1	3	4	3	4	3	4	3	3	3	3
+2	2	2	2	2	2	2	2	2	2	2	2	2	2	2	2
+3	1	1	2	3	1	1	2	1	2	5	1	1	1	1	1
+3	2	2	3	3	2	2	3	2	3	4	5	2	2	2	2
+3	3	3	3	3	3	3	3	3	3	3	3	3	3	3	3
+3	4	3	4	3	4	3	4	3	4	3	4	3	3	3	3
+4	3	1	2	4	3	1	2	1	2	5	1	1	1	1	1
+4	4	4	4	4	4	4	4	4	4	4	4	4	4	4	4
+explain
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t3.b and t4.b = t2.c;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	PRIMARY	NULL	NULL	NULL	16	Parent of 4 pushed join@1
+1	SIMPLE	t2	eq_ref	PRIMARY	PRIMARY	8	test.t1.a,test.t1.b	1	Child of pushed join@1
+1	SIMPLE	t3	eq_ref	PRIMARY	PRIMARY	8	test.t1.c,test.t1.d	1	Child of pushed join@1
+1	SIMPLE	t4	eq_ref	PRIMARY	PRIMARY	8	test.t1.d,test.t2.c	1	Child of pushed join@1
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t3.b and t4.b = t2.c;
+a	b	c	d	a	b	c	d	a	b	c	d	a	b	c	d
+1	1	1	1	1	1	1	1	1	1	1	1	1	1	1	1
+1	3	1	2	1	3	1	2	1	2	5	1	2	1	3	4
+1	4	2	3	1	4	2	3	2	3	4	5	3	2	2	3
+2	1	3	4	2	1	3	4	3	4	3	4	4	3	1	2
+2	2	2	2	2	2	2	2	2	2	2	2	2	2	2	2
+3	1	1	2	3	1	1	2	1	2	5	1	2	1	3	4
+3	2	2	3	3	2	2	3	2	3	4	5	3	2	2	3
+3	3	3	3	3	3	3	3	3	3	3	3	3	3	3	3
+3	4	3	4	3	4	3	4	3	4	3	4	4	3	1	2
+4	3	1	2	4	3	1	2	1	2	5	1	2	1	3	4
+4	4	4	4	4	4	4	4	4	4	4	4	4	4	4	4
+explain
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t3.c and t4.b = t2.a;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	PRIMARY	NULL	NULL	NULL	16	Parent of 4 pushed join@1
+1	SIMPLE	t2	eq_ref	PRIMARY	PRIMARY	8	test.t1.a,test.t1.b	1	Child of pushed join@1
+1	SIMPLE	t3	eq_ref	PRIMARY	PRIMARY	8	test.t1.c,test.t1.d	1	Child of pushed join@1
+1	SIMPLE	t4	eq_ref	PRIMARY	PRIMARY	8	test.t3.c,test.t1.a	1	Child of pushed join@1
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t3.c and t4.b = t2.a;
+a	b	c	d	a	b	c	d	a	b	c	d	a	b	c	d
+1	1	1	1	1	1	1	1	1	1	1	1	1	1	1	1
+1	4	2	3	1	4	2	3	2	3	4	5	4	1	4	5
+2	1	3	4	2	1	3	4	3	4	3	4	3	2	2	3
+2	2	2	2	2	2	2	2	2	2	2	2	2	2	2	2
+3	2	2	3	3	2	2	3	2	3	4	5	4	3	1	2
+3	3	3	3	3	3	3	3	3	3	3	3	3	3	3	3
+3	4	3	4	3	4	3	4	3	4	3	4	3	3	3	3
+4	4	4	4	4	4	4	4	4	4	4	4	4	4	4	4
+explain
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t3.c and t4.b = t2.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	PRIMARY	NULL	NULL	NULL	16	Parent of 4 pushed join@1
+1	SIMPLE	t2	eq_ref	PRIMARY	PRIMARY	8	test.t1.a,test.t1.b	1	Child of pushed join@1
+1	SIMPLE	t3	eq_ref	PRIMARY	PRIMARY	8	test.t1.c,test.t1.d	1	Child of pushed join@1
+1	SIMPLE	t4	eq_ref	PRIMARY	PRIMARY	8	test.t3.c,test.t1.b	1	Child of pushed join@1
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t3.c and t4.b = t2.b;
+a	b	c	d	a	b	c	d	a	b	c	d	a	b	c	d
+1	1	1	1	1	1	1	1	1	1	1	1	1	1	1	1
+1	4	2	3	1	4	2	3	2	3	4	5	4	4	4	4
+2	1	3	4	2	1	3	4	3	4	3	4	3	1	1	2
+2	2	2	2	2	2	2	2	2	2	2	2	2	2	2	2
+3	2	2	3	3	2	2	3	2	3	4	5	4	2	5	1
+3	3	3	3	3	3	3	3	3	3	3	3	3	3	3	3
+3	4	3	4	3	4	3	4	3	4	3	4	3	4	3	4
+4	4	4	4	4	4	4	4	4	4	4	4	4	4	4	4
+explain
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t1.c and t4.b = t2.c;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	PRIMARY	NULL	NULL	NULL	16	Parent of 4 pushed join@1
+1	SIMPLE	t2	eq_ref	PRIMARY	PRIMARY	8	test.t1.a,test.t1.b	1	Child of pushed join@1
+1	SIMPLE	t3	eq_ref	PRIMARY	PRIMARY	8	test.t1.c,test.t1.d	1	Child of pushed join@1
+1	SIMPLE	t4	eq_ref	PRIMARY	PRIMARY	8	test.t3.a,test.t2.c	1	Child of pushed join@1; Using where
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t1.c and t4.b = t2.c;
+a	b	c	d	a	b	c	d	a	b	c	d	a	b	c	d
+1	1	1	1	1	1	1	1	1	1	1	1	1	1	1	1
+1	3	1	2	1	3	1	2	1	2	5	1	1	1	1	1
+1	4	2	3	1	4	2	3	2	3	4	5	2	2	2	2
+2	1	3	4	2	1	3	4	3	4	3	4	3	3	3	3
+2	2	2	2	2	2	2	2	2	2	2	2	2	2	2	2
+3	1	1	2	3	1	1	2	1	2	5	1	1	1	1	1
+3	2	2	3	3	2	2	3	2	3	4	5	2	2	2	2
+3	3	3	3	3	3	3	3	3	3	3	3	3	3	3	3
+3	4	3	4	3	4	3	4	3	4	3	4	3	3	3	3
+4	3	1	2	4	3	1	2	1	2	5	1	1	1	1	1
+4	4	4	4	4	4	4	4	4	4	4	4	4	4	4	4
+explain
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t3.c and t4.b = t1.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	PRIMARY	NULL	NULL	NULL	16	Parent of 4 pushed join@1
+1	SIMPLE	t2	eq_ref	PRIMARY	PRIMARY	8	test.t1.a,test.t1.b	1	Child of pushed join@1
+1	SIMPLE	t3	eq_ref	PRIMARY	PRIMARY	8	test.t1.c,test.t1.d	1	Child of pushed join@1
+1	SIMPLE	t4	eq_ref	PRIMARY	PRIMARY	8	test.t3.c,test.t2.b	1	Child of pushed join@1; Using where
+select straight_join *
+from t1
+join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+join t1 as t4 on t4.a = t3.c and t4.b = t1.b;
+a	b	c	d	a	b	c	d	a	b	c	d	a	b	c	d
+1	1	1	1	1	1	1	1	1	1	1	1	1	1	1	1
+1	4	2	3	1	4	2	3	2	3	4	5	4	4	4	4
+2	1	3	4	2	1	3	4	3	4	3	4	3	1	1	2
+2	2	2	2	2	2	2	2	2	2	2	2	2	2	2	2
+3	2	2	3	3	2	2	3	2	3	4	5	4	2	5	1
+3	3	3	3	3	3	3	3	3	3	3	3	3	3	3	3
+3	4	3	4	3	4	3	4	3	4	3	4	3	4	3	4
+4	4	4	4	4	4	4	4	4	4	4	4	4	4	4	4
+set ndb_join_pushdown=true;
 explain 
 select * from t1 x, t1 y, t1 z, t1 where 
 y.a=x.d and y.b=x.b and 

=== modified file 'mysql-test/suite/ndb/t/ndb_join_pushdown.test'
--- a/mysql-test/suite/ndb/t/ndb_join_pushdown.test	2010-06-15 12:30:28 +0000
+++ b/mysql-test/suite/ndb/t/ndb_join_pushdown.test	2010-06-21 08:11:42 +0000
@@ -382,6 +382,160 @@ from t1 as t2
 join t1 as t3 on t3.a = t2.c and t3.b = t2.d
 where t2.a = 1;
 
+# Test multiparent pushed joins where it is not possible
+# to find a single common parent by using the equality set
+#
+# NOTE: We should take care to join the multiparent linked
+# table on field refs. not also being refered from other join expr.
+# as this will make them candidates for equality set replacement.
+#
+set ndb_join_pushdown=true;
+
+# t3 refer both t1,t2 as parrent.
+# t1 should be identifed as a grandparent available
+# through its child t2.
+explain
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t2.c and t3.b = t1.c;
+--sorted_result
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t2.c and t3.b = t1.c;
+
+# t4 is NOT pushable as t2 & t3 does not have 
+# a parent / grandparent relationship
+explain
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t3.c and t4.b = t2.c;
+--sorted_result
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t3.c and t4.b = t2.c;
+
+# t3 is a child of t2 and grandchild of t1
+# t4 is a child of t3 and grandchild of t2
+explain
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t2.d
+ join t1 as t4 on t4.a = t3.c and t4.b = t2.c;
+--sorted_result
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t2.d
+ join t1 as t4 on t4.a = t3.c and t4.b = t2.c;
+
+# t3 is a child of t2 and grandchild of t1
+# t4 is a child of t3 and grandgrandchild of t1
+explain
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t2.d
+ join t1 as t4 on t4.a = t3.c and t4.b = t1.d;
+--sorted_result
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t2.d
+ join t1 as t4 on t4.a = t3.c and t4.b = t1.d;
+
+# Some testcases where t4 is not directly pushable, but
+# may be made pushable by equality set replacement.
+#
+# BEWARE: mysqld optimizer may do its own replacement
+#    before ha_ndbcluster analyze the AQP. We therefore 
+#    provide multiple similar testcases and hope that
+#    some of them will trigger the replacement code in 
+#    ha_ndbcluster :-o
+explain
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t3.a and t4.b = t2.c;
+--sorted_result
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t3.a and t4.b = t2.c;
+
+explain
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t3.b and t4.b = t2.c;
+--sorted_result
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t3.b and t4.b = t2.c;
+
+explain
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t3.c and t4.b = t2.a;
+--sorted_result
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t3.c and t4.b = t2.a;
+
+explain
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t3.c and t4.b = t2.b;
+--sorted_result
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t3.c and t4.b = t2.b;
+
+explain
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t1.c and t4.b = t2.c;
+--sorted_result
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t1.c and t4.b = t2.c;
+
+explain
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t3.c and t4.b = t1.b;
+--sorted_result
+select straight_join *
+from t1
+ join t1 as t2 on t2.a = t1.a and t2.b = t1.b
+ join t1 as t3 on t3.a = t1.c and t3.b = t1.d
+ join t1 as t4 on t4.a = t3.c and t4.b = t1.b;
+
 
 # Test a combination of pushed table scan (x, y)
 #  & pushed EQ-bound (indexScan) (z, t1)

=== modified file 'sql/abstract_query_plan.cc'
--- a/sql/abstract_query_plan.cc	2010-05-05 13:20:48 +0000
+++ b/sql/abstract_query_plan.cc	2010-06-21 08:13:31 +0000
@@ -72,34 +72,6 @@ namespace AQP
     return m_join_tabs + join_tab_no;
   }
 
-  /**
-    Find the table that a given Item_field refers to. 
-    Returns a negative value if field_item does not refer a
-    table within this Join_plan.
-  */
-  const Table_access*
-  Join_plan::get_referred_table_access(const Item_field* field_item,
-                                       const Table_access* start_tab) const
-  {
-    DBUG_ASSERT(field_item->type() == Item::FIELD_ITEM);
-    DBUG_ASSERT(start_tab >= m_table_accesses &&
-                start_tab < m_table_accesses+get_access_count());
-
-    table_map used_tables = field_item->used_tables();
-
-    /* Early elimination of outer referrences. */
-//  if (used_tables & OUTER_REF_TABLE_BIT)
-//    return NULL;
-
-    const Table_access* end_tab= m_table_accesses+get_access_count();
-    for (const Table_access* tab= start_tab; tab < end_tab; tab++)
-    {
-      if (tab->get_join_tab()->table->map == used_tables)
-        return tab;
-    }
-    return NULL;
-  }
-
   void
   Join_plan::find_skippabable_group_or_order() const
   {

=== modified file 'sql/abstract_query_plan.h'
--- a/sql/abstract_query_plan.h	2010-03-24 00:18:08 +0000
+++ b/sql/abstract_query_plan.h	2010-06-21 08:13:31 +0000
@@ -19,6 +19,7 @@
 #ifndef ABSTRACT_QUERY_PLAN_H_INCLUDED
 #define ABSTRACT_QUERY_PLAN_H_INCLUDED
 
+struct st_table;
 struct st_join_table;
 typedef st_join_table JOIN_TAB;
 class JOIN;
@@ -69,10 +70,6 @@ namespace AQP
 
     uint get_access_count() const;
 
-    const Table_access* 
-    get_referred_table_access(const Item_field* field_item,
-                              const Table_access* base ) const;
-
     /**
       Can filesort(), normally required by execution of GROUP BY 
       or ORDER BY, be skipped due to the columns already being
@@ -200,7 +197,7 @@ namespace AQP
 
   private:
 
-    /** The first access operation in the plan. */
+    /** Backref. to the Join_plan which this Table_access is part of */
     const Join_plan* m_join_plan;
 
     /** This operation corresponds to m_root_tab[m_tab_no].*/

=== modified file 'sql/ha_ndbcluster.cc'
--- a/sql/ha_ndbcluster.cc	2010-06-08 10:47:39 +0000
+++ b/sql/ha_ndbcluster.cc	2010-06-21 09:33:55 +0000
@@ -236,16 +236,148 @@ private:
 
 /** 
  * This type is used in conjunction with AQP::Join_plan and represents a set 
- * of the table access operations of the join plan.
+ * of the table access operations of the join plan. 
+ * Had to subclass Bitmap as the default Bitmap<64> c'tor didn't initialize its
+ * map.
  */
-typedef Bitmap<(MAX_TABLES > 64 ? MAX_TABLES : 64)> ndb_table_access_map;
+#define TABLE_BITMAP Bitmap<(MAX_TABLES > 64 ? MAX_TABLES : 64)>
 
-/** This class represents a pushed (N-way) join operation.*/
+class ndb_table_access_map : public TABLE_BITMAP
+{
+public:
+  explicit ndb_table_access_map()
+   : TABLE_BITMAP(0)
+  {}
+
+  explicit ndb_table_access_map(table_map bitmap)
+   : TABLE_BITMAP(0)
+//{ set_map(table); } .. Bitmap<>::set_map() is expected to be available in the near future
+  {
+    for (uint i= 0; bitmap!=0; i++, bitmap>>=1)
+    {
+      if (bitmap & 1)
+        set_bit(i);
+    }
+  }
+
+  explicit ndb_table_access_map(const AQP::Table_access* const table)
+   : TABLE_BITMAP(0)
+  { set_bit(table->get_table()->tablenr); }
+
+  void add(const ndb_table_access_map& table_map)
+  { // Require const_cast as signature of class Bitmap::merge is not const correct
+    merge(const_cast<ndb_table_access_map&>(table_map));
+  }
+  void add(const AQP::Table_access* const table)
+  {
+    ndb_table_access_map table_map(table);
+    add(table_map);
+  }
+
+  bool contain(const ndb_table_access_map& table_map) const
+  {
+    return table_map.is_subset(*this);
+  }
+  bool contain_table(const AQP::Table_access* const table) const
+  {
+    ndb_table_access_map table_map(table);
+    return contain(table_map);
+  }
+}; // class ndb_table_access_map
+
+
+/**
+ * Contains the context and helper methods used during ::make_pushed_join().
+ *
+ * Interacts with the AQP which provides interface to the query prepared by
+ * the mysqld optimizer.
+ *
+ * Execution plans built for pushed joins are stored inside this builder context.
+ */
+class ndb_pushed_builder_ctx
+{
+public:
+  ndb_pushed_builder_ctx(AQP::Join_plan& plan, const AQP::Table_access* const join_root)
+   : m_plan(plan), m_join_root(join_root), m_join_scope(join_root), m_const_scope()
+  {
+    for (uint i= 0; i<join_root->get_access_no(); i++)
+      m_const_scope.add(plan.get_table_access(i));
+  }
+
+  const AQP::Join_plan& plan() const
+  { return m_plan; }
+
+  const AQP::Table_access* join_root() const
+  { return m_join_root; }
+
+  const ndb_table_access_map& join_scope() const
+  { return m_join_scope; }
+
+  const ndb_table_access_map& const_scope() const
+  { return m_const_scope; }
+
+  const NdbQueryOperationDef* get_query_operation(const AQP::Table_access* table) const
+  { return m_tables[table->get_access_no()].op; }
+
+  const AQP::Table_access* get_referred_table_access(
+                  const ndb_table_access_map& used_tables) const;
+
+  void add_parent_candidate(
+                  const ndb_table_access_map& table,
+                  const ndb_table_access_map& old_parents,
+                  ndb_table_access_map& parents) const;
+
+  bool field_ref_is_join_pushable(
+                  const AQP::Table_access* table,
+                  const Item* join_items[],
+                  const AQP::Table_access*& parent);
+
+  void add_pushed(const AQP::Table_access* table,
+                  const AQP::Table_access* parent,
+                  const NdbQueryOperationDef* query_op);
+
+  // Check if 'table' is child of some of the specified 'parents'
+  bool is_child_of(const AQP::Table_access* table,
+                   const ndb_table_access_map& parents) const
+  {
+    DBUG_ASSERT(m_join_scope.contain_table(table));
+    DBUG_ASSERT(parents.is_subset(m_join_scope));
+    return parents.is_overlapping(m_tables[table->get_access_no()].m_ancestors);
+  }
+
+private:
+  const AQP::Join_plan& m_plan;
+  const AQP::Table_access* const m_join_root;
+
+  // Scope of tables covered by this pushed join
+  ndb_table_access_map m_join_scope;
+
+  // Scope of tables evaluated prior to 'm_join_root'
+  // These are effectively const or params wrt. the pushed join
+  ndb_table_access_map m_const_scope;
+
+  struct pushed_tables
+  {
+    pushed_tables() : m_ancestors(), op(NULL) {}
+    ndb_table_access_map m_ancestors;
+    const NdbQueryOperationDef* op;
+  } m_tables[MAX_TABLES];
+
+}; // class ndb_pushed_builder_ctx
+
+
+/** This class represents a prepared pushed (N-way) join operation.
+ *
+ *  It might be instantiated multiple times whenever the query,
+ *  or this subpart of the query, is being (re-)executed by
+ *  ::createQuery() or it's wrapper method 
+ *  ha_ndbcluster::create_pushed_join().
+ */
 class ndb_pushed_join
 {
 public:
   ndb_pushed_join(const AQP::Join_plan& plan, 
-                 ndb_table_access_map& pushed_operations,
+                 const ndb_table_access_map& pushed_operations,
                  uint field_refs, Field* const fields[],
                  const NdbQueryDef* query_def)
   :
@@ -258,7 +390,7 @@ public:
     for (uint i= 0; i < plan.get_access_count(); i++)
     {
       const AQP::Table_access* const join_tab= plan.get_table_access(i);
-      if (pushed_operations.is_set(join_tab->get_access_no()))
+      if (pushed_operations.contain_table(join_tab))
       {
         DBUG_ASSERT(m_operation_count < MAX_PUSHED_OPERATIONS);
         m_tables[m_operation_count++] = join_tab->get_table();
@@ -355,16 +487,103 @@ const char* get_referred_table_access_na
 }
 
 
+void
+ndb_pushed_builder_ctx::add_pushed(
+                  const AQP::Table_access* table,
+                  const AQP::Table_access* parent,
+                  const NdbQueryOperationDef* query_op)
+{
+  uint table_no= table->get_access_no();
+  DBUG_ASSERT(table_no < MAX_TABLES);
+  m_join_scope.add(table);
+  m_tables[table_no].op= query_op;
+  if (likely(parent))
+  {
+    // Aggregated set of parent and grand...grand...parents to this table
+    ndb_table_access_map parent_map(parent);
+    DBUG_ASSERT(m_join_scope.contain(parent_map));
+    m_tables[table_no].m_ancestors= m_tables[parent->get_access_no()].m_ancestors;
+    m_tables[table_no].m_ancestors.add(parent_map);
+  }
+  else
+  {
+    m_tables[table_no].m_ancestors.clear_all();
+  }
+}
+
+/**
+ *  get_referred_table_access()
+ *
+ * Locate the 'Table_access' object for table with the specified bitmap id
+ */
+const AQP::Table_access*
+ndb_pushed_builder_ctx::get_referred_table_access(const ndb_table_access_map& find_table) const
+{
+  for (uint i= join_root()->get_access_no(); i < m_plan.get_access_count(); i++)
+  {
+    const AQP::Table_access* const table= m_plan.get_table_access(i);
+    ndb_table_access_map table_map(table);
+    if (m_join_scope.contain(table_map) && find_table==table_map)
+      return table;
+  }
+  return NULL;
+}
+
+/**
+ *  add_parent_candidate()
+ *
+ *  We have located a ref. to a parent table within our pushed join scope.
+ *  Due to the possible equality set replacement which may take place, we may
+ *  have multiple parent candidates for a single key_item in the join condition.
+ *  
+ *  Update our set of 'parents' references such that it only contain parent
+ *  tables which are/may be directly refered by all key_items in the join condition
+ *    or:
+ *  the table refered by the join conditin should be reachable as a grand parent
+ *  of the table(s) in the parents list.
+ *
+ *  - table: bitmap of table being added as parent candidate.
+ *  - old_parents: Set of parents calculated for all the previous key_items.
+ *  - parents: Set of parent candidates currently being calculated for this key_item.
+ */
+void
+ndb_pushed_builder_ctx::add_parent_candidate(const ndb_table_access_map& table,
+                                             const ndb_table_access_map& old_parents,
+                                             ndb_table_access_map& parents) const
+
+{
+  DBUG_ASSERT(m_join_scope.contain(table));
+  if (!parents.contain(table)) // referred_table not already parent
+  {
+    if (old_parents.is_clear_all()  ||  // Initial assignment
+        old_parents.contain(table))     // Parent is common
+    {
+      parents.add(table);
+    }
+    else
+    {
+      const AQP::Table_access* const referred_table= get_referred_table_access(table);
+      ndb_table_access_map grandparents= m_tables[referred_table->get_access_no()].m_ancestors;
+      if (grandparents.is_overlapping(old_parents)) // Parent is real parent
+      {
+        parents.add(table);
+      }
+    }
+  }
+}
+
+
 /***************************************************************
  *  field_ref_is_join_pushable()
  *
- * Determines if the specified child ('child_access') can be appended to 
+ * Determines if the specified child ('table') can be appended to 
  * an existing chain of previously pushed join operations.
  *
  * To be considdered pushable the child operation should:
  *
  *  1) Have an EQ_REF to the previous parent operations.
- *  2) Refer only a single parent through it EQ_REF's
+ *  2) Refer only a single parent, or a grandparent reachable through 
+ *     a single parent common to all key fields in the 'EQ_REF'
  *
  * In order to increase pushability we use the COND_EQUAL sets 
  * to resolve cases (2) above) where multiple parents are refered.
@@ -373,25 +592,23 @@ const char* get_referred_table_access_na
  * it pushable . The modified join condition is returned in 
  * join_items[] .
  ****************************************************************/
-static bool
-field_ref_is_join_pushable(const AQP::Join_plan& plan, 
-                           const AQP::Table_access* const join_root,
-                           const ndb_table_access_map parent_scope,
-                           const AQP::Table_access* child_access,
+bool
+ndb_pushed_builder_ctx::field_ref_is_join_pushable(
+                           const AQP::Table_access* table,
                            const Item* join_items[ndb_pushed_join::MAX_LINKED_KEYS+1],
                            const AQP::Table_access*& parent)
 {
   DBUG_ENTER("field_ref_is_join_pushable");
-  uint tab_no = child_access->get_access_no();
+  uint tab_no = table->get_access_no();
   parent= NULL;
 
-  DBUG_ASSERT (join_root < child_access);
+  DBUG_ASSERT (join_root() < table);
 
-  if (child_access->get_access_type() != AQP::AT_PRIMARY_KEY &&
-      child_access->get_access_type() != AQP::AT_UNIQUE_KEY)
+  if (table->get_access_type() != AQP::AT_PRIMARY_KEY &&
+      table->get_access_type() != AQP::AT_UNIQUE_KEY)
     DBUG_RETURN(false);
 
-  if (child_access->get_no_of_key_fields() > ndb_pushed_join::MAX_LINKED_KEYS)
+  if (table->get_no_of_key_fields() > ndb_pushed_join::MAX_LINKED_KEYS)
   {
     DBUG_PRINT("info", ("  'key_parts >= ndb_pushed_join::MAX_LINKED_KEYS', "
                         "can't append table:%d", tab_no+1));
@@ -399,40 +616,22 @@ field_ref_is_join_pushable(const AQP::Jo
   }
 
   DBUG_PRINT("info", ("Table:%d, Checking %d EQ_REF keys", tab_no, 
-                      child_access->get_no_of_key_fields()));
-
-  /**
-   * Explore child references to parent operation(s). For all FIELD_ITEMs
-   * aggregate the 'table->map' bitmask for all possible parent table ref's.
-   * Such that the refered table may be used as a source for all child refs.
-   *   *
-   * 1) Collect (or-aggregate) the set of parents currently referred
-   *    by table_access->ref in 'current_parents'.
-   *
-   * 2) For each FIELD_ITEM in childs 'ref.key' aggregate 'table->map'
-   *    bitmask for the table currently refered and alternative parents 
-   *    from COND_EQUAL in 'field_possible_parents'
-   *
-   * 3) Collect (and-aggregate) the total set of candidate parents being
-   *    the single parent for this child in 'all_linked_parents'
-   *    
-   **/
+                      table->get_no_of_key_fields()));
 
-  bool multiple_parents= false;
-  bool outside_scope= false;
-  ndb_table_access_map current_parents(0);
-  ndb_table_access_map all_common_parents(0);
+  ndb_table_access_map current_parents;
+  ndb_table_access_map parents;
 
   for (uint key_part_no= 0; 
-       key_part_no < child_access->get_no_of_key_fields(); 
+       key_part_no < table->get_no_of_key_fields(); 
        key_part_no++)
   {
     /* All parts of the key must be fields in some of the preceeding 
      * tables 
      */
-    const Item* const key_item= child_access->get_key_field(key_part_no);
+    const Item* const key_item= table->get_key_field(key_part_no);
     join_items[key_part_no]= key_item;
 
+    DBUG_ASSERT(key_item->const_item() == key_item->const_during_execution());
     if (key_item->const_item())  // ...->const_during_execution() ?
     {
       DBUG_PRINT("info", (" Item type:%d is 'const_item'", key_item->type()));
@@ -453,44 +652,60 @@ field_ref_is_join_pushable(const AQP::Jo
                         key_item_field->field->field_name));
 
     if (!key_item_field->field
-        ->eq_def(child_access->get_key_part_info(key_part_no)->field))
+        ->eq_def(table->get_key_part_info(key_part_no)->field))
     {
       DBUG_PRINT("info", ("Item_field does not have same definition as EQ_REF'ed key"));
       DBUG_RETURN(false);
     }
 
-    // 1) Calculate current parent referrences
-    ndb_table_access_map field_possible_parents(0);
-    const AQP::Table_access* const referred_table= 
-      plan.get_referred_table_access(key_item_field,join_root);
-    if (referred_table && 
-        (parent_scope.is_set(referred_table->get_access_no())))
-    {
-      field_possible_parents.set_bit(referred_table->get_access_no());
-      current_parents.set_bit(referred_table->get_access_no());
-      multiple_parents= !(current_parents == field_possible_parents);
-      parent= referred_table;  // Assumed until further
-    }
-    else
-    {
-      outside_scope= true;
-    }
+    /**
+     * Below this point 'key_item_field' is a candidate for refering a parent table
+     * in a pushed join. It should either directly refer a parent common to all
+     * FIELD_ITEMs, or refer a grandparent of this common parent.
+     * There are different cases which should be handled:
+     *
+     *  1) 'key_item_field' may already refer one of the parent available within our
+     *      pushed scope.
+     *  2)  By using the equality set, we may find alternative parent references which
+     *      may make this a pushed join.
+     *  3)  In the steps above we may have found parent references which was 
+     *      temp. rejected as they were not in the set of common parents (old_parents).
+     *      Take a second look at rejected 'old_parents' and add any of these which refer
+     *      a parent as a grandparent.
+     */
 
-    // 2) Aggregate alternative parents from equality set
-    AQP::Equal_set_iterator equal_iter(&plan, key_item_field);
+    ///////////////////////////////////////////////////////////////////
+    // 0) Prepare for calculating parent candidates for this FIELD_ITEM
+    //
+    ndb_table_access_map field_possible_parents;
+    ndb_table_access_map old_parents(parents);
+    parents.clear_all();
+
+    ////////////////////////////////////////////////////////////////////
+    // 1) Add our existing parent reference to the set of parent candidates
+    //
+    ndb_table_access_map parent_map(key_item_field->used_tables());
+    current_parents.add(parent_map);
+
+    if (m_join_scope.contain(parent_map))
+    {
+      field_possible_parents.add(parent_map);
+      add_parent_candidate (parent_map, old_parents, parents);
+    }
+
+    //////////////////////////////////////////////////////////////////
+    // 2) Use the equality set to possibly find more parent candidates
+    //    usable by substituting existing 'key_item_field'
+    //
+    AQP::Equal_set_iterator equal_iter(&m_plan, key_item_field);
     const Item_field* substitute_field= equal_iter.next();
     while (substitute_field != NULL)
     {
       if (substitute_field != key_item_field)
       {
-        const AQP::Table_access* const substitute_table= 
-          plan.get_referred_table_access(substitute_field,join_root);
-        if (substitute_table && 
-            (parent_scope.is_set(substitute_table->get_access_no())) &&
-            substitute_table->get_access_no() < tab_no)
+        ndb_table_access_map substitute_table(substitute_field->used_tables());
+        if (m_join_scope.contain(substitute_table))
         {
-          // Substitute is inside linked scope
-          field_possible_parents.set_bit(substitute_table->get_access_no());
           DBUG_PRINT("info", 
                      (" join_items[%d] %s.%s can be replaced with %s.%s",
                       key_part_no,
@@ -498,25 +713,36 @@ field_ref_is_join_pushable(const AQP::Jo
                       get_referred_field_name(key_item_field),
                       get_referred_table_access_name(substitute_field),
                       get_referred_field_name(substitute_field)));
+
+          field_possible_parents.add(substitute_table);
+          add_parent_candidate (substitute_table, old_parents, parents);
         }
       }
       substitute_field= equal_iter.next();
     } // while(substitute_field != NULL)
 
-    // 3) Aggregate set of possible single parent candidates serving all child refs
+    ////////////////////////////////////////////////////////////////////////////////
+    // 3) Handle possible rejected 'old_parents' which refer any possible_parents as
+    //    a grandparent
     if (!field_possible_parents.is_clear_all())
     {
-      if (all_common_parents.is_clear_all())
-        all_common_parents= field_possible_parents;
-      else
+      old_parents.subtract(parents);
+      if (!old_parents.is_clear_all())
       {
-        all_common_parents.intersect(field_possible_parents);
-        if (all_common_parents.is_clear_all())
-          break;
+        // Add all 'old_parents[parent_no]' with some of 'field_possible_parents' as grandparents
+        for (uint parent_no= join_root()->get_access_no(); parent_no < tab_no; parent_no++)
+        {
+          const AQP::Table_access* const table = m_plan.get_table_access(parent_no);
+          if (old_parents.contain_table(table) && is_child_of(table,field_possible_parents))
+          {
+            parents.add(table);
+          }
+        }
       }
+      if (parents.is_clear_all())
+        break;
     }
-    else if (referred_table && 
-             !(parent_scope.is_set(referred_table->get_access_no())))
+    else if (!m_const_scope.contain(parent_map))
     {
       DBUG_PRINT("info", ("Item_field %s.%s is outside scope of pushable join",
                   get_referred_table_access_name(key_item_field),
@@ -525,7 +751,9 @@ field_ref_is_join_pushable(const AQP::Jo
     }
   } // for (uint key_part_no= 0 ...
 
-  if (all_common_parents.is_clear_all())
+  join_items[table->get_no_of_key_fields()]= NULL;
+
+  if (parents.is_clear_all())
   {
     // NOTE: This is a constant table in the context of this pushed join.
     //       It should be relatively simple to extend the SPJ block to 
@@ -534,40 +762,33 @@ field_ref_is_join_pushable(const AQP::Jo
     DBUG_RETURN(false);
   }
 
+  DBUG_ASSERT(parents.is_subset(m_join_scope));
+
   /**
-   * Based on information we have aggregated above, resolve conditions where either:
-   *  - Multiple parents are refered from this child.
-   *  - Parent refered from this child is outside scope of visible parents.
-   *
-   * Situation is resolved by:
-   *
-   * 1) New (common) parent is selected as the 'most grand-' parent
-   *    in 'all_linked_parents' (new_parent)
-   *
-   * 2) For any FIELD_ITEMs in join_items[] not already refering a field from new_parent,
-   *    update join_items[] to refer the correct new_parent field available from the
-   *    equality set.
+   * Parent is selected among the set of 'parents'. To improve
+   * fanout (bushy joins!) we prefer the first of the available parents.
+   */
+  uint parent_no;
+  for (parent_no= join_root()->get_access_no();
+       parent_no < tab_no && !parents.contain_table(m_plan.get_table_access(parent_no));
+       parent_no++)
+  {}
+  parent= m_plan.get_table_access(parent_no);
+  ndb_table_access_map parent_map(parent);
+  DBUG_ASSERT(parents.contain(parent_map));
+
+  /**
+   * If there are any key_fields with 'current_parents' different from
+   * our selected 'parent', we have to find substitutes for
+   * those key_fields within the equality set.
    **/
-  if (multiple_parents || outside_scope)
+  if (!(parent_map==current_parents))
   {
-    DBUG_ASSERT(!all_common_parents.is_clear_all());
-    DBUG_ASSERT(all_common_parents.is_subset(parent_scope));
-
-    // 1) Parent should be first table found in 'all_linked_parents'
-    const AQP::Table_access* new_parent= NULL;
+    ndb_table_access_map grandparent_map= m_tables[parent_no].m_ancestors;
+    DBUG_ASSERT (!grandparent_map.contain(parent_map));
 
-    for (uint i= join_root->get_access_no(); i < tab_no; i++)
-    {
-      new_parent= plan.get_table_access(i);
-      if (all_common_parents.is_set(new_parent->get_access_no()))
-        break;
-    }
-    DBUG_ASSERT(all_common_parents.is_set(new_parent->get_access_no()));
-    parent= new_parent;
-
-    // 2) Update join_items[] not already refering new_parent
     for (uint key_part_no= 0; 
-         key_part_no < child_access->get_no_of_key_fields(); 
+         key_part_no < table->get_no_of_key_fields(); 
          key_part_no++)
     {
       DBUG_ASSERT(join_items[key_part_no]->const_item() || 
@@ -575,23 +796,24 @@ field_ref_is_join_pushable(const AQP::Jo
 
       if (join_items[key_part_no]->type() == Item::FIELD_ITEM)
       {
-        const Item_field* const join_item 
+        const Item_field* join_item 
           = static_cast<const Item_field*>(join_items[key_part_no]);
         
-        const AQP::Table_access* const referred_table= 
-          plan.get_referred_table_access(join_item,join_root);
-        if (referred_table != new_parent)
+        ndb_table_access_map used_table(join_item->used_tables());
+        if (!(used_table == parent_map))
         {
-          AQP::Equal_set_iterator iter(&plan, join_item);
+          AQP::Equal_set_iterator iter(&m_plan, join_item);
           const Item_field* substitute_field= iter.next();
           while (substitute_field != NULL)
           {
-            const AQP::Table_access* const substitute_table= 
-              plan.get_referred_table_access(substitute_field,join_root);
-            if (substitute_table == new_parent)
+            ///////////////////////////////////////////////////////////
+            // Prefer to replace join_item with ref. to selected parent.
+            //
+            ndb_table_access_map substitute_table(substitute_field->used_tables());
+            if (substitute_table == parent_map)
             {
               DBUG_PRINT("info", 
-                         (" Replacing join_items[%d] %s.%s with %s.%s",
+                         (" Replacing join_items[%d] %s.%s with %s.%s (parent)",
                           key_part_no,
                           //join_item->field->table->alias,
                           get_referred_table_access_name(join_item),
@@ -599,20 +821,34 @@ field_ref_is_join_pushable(const AQP::Jo
                           get_referred_table_access_name(substitute_field),
                           get_referred_field_name(substitute_field)));
 
-              join_items[key_part_no]= substitute_field;
-              current_parents.set_bit(substitute_table->get_access_no());
+              join_items[key_part_no]= join_item= substitute_field;
               break;
             }
+
+            ////////////////////////////////////////////////////////////
+            // Second best is to replace join_item with grandparent ref.
+            // In this case we will continue to search for a common parent match
+            //
+            else if (!grandparent_map.contain(used_table) && grandparent_map.contain(substitute_table))
+            {
+              DBUG_PRINT("info", 
+                         (" Replacing join_items[%d] %s.%s with %s.%s (grandparent)",
+                          key_part_no,
+                          //join_item->field->table->alias,
+                          get_referred_table_access_name(join_item),
+                          get_referred_field_name(join_item),
+                          get_referred_table_access_name(substitute_field),
+                          get_referred_field_name(substitute_field)));
+
+              join_items[key_part_no]= join_item= substitute_field;
+            }
             substitute_field= iter.next();
           }
         }
-      }
+      } // Item::FIELD_ITEM
     } // for all 'key_parts'
-  }
-  DBUG_ASSERT(parent_scope.is_set(parent->get_access_no()));
-  DBUG_ASSERT(current_parents.is_set(parent->get_access_no()));
+  } // substitute
 
-  join_items[child_access->get_no_of_key_fields()]= NULL;
   DBUG_RETURN(true);
 } // field_ref_is_join_pushable
 
@@ -710,9 +946,6 @@ ha_ndbcluster::make_pushed_join(AQP::Joi
   DBUG_PRINT("info", ("join_root is pushable:"));
   DBUG_EXECUTE("info", join_root->dbug_print(););
 
-  ndb_table_access_map pushed_scope(0);
-  pushed_scope.set_bit(join_root->get_access_no());
-
   /**
    * Past this point we know at least join_root to be join pushable 
    * as parent operation. Search for tables to be appendable as child 
@@ -721,7 +954,7 @@ ha_ndbcluster::make_pushed_join(AQP::Joi
    * appendable child.
    */
   NdbQueryBuilder builder(*m_thd_ndb->ndb);
-  const NdbQueryOperationDef* operation_defs[ndb_pushed_join::MAX_PUSHED_OPERATIONS];
+  ndb_pushed_builder_ctx context(plan, join_root);
 
   uint push_cnt= 0;
   uint fld_refs= 0;
@@ -730,9 +963,10 @@ ha_ndbcluster::make_pushed_join(AQP::Joi
   for (uint join_cnt= join_root->get_access_no()+1; join_cnt<plan.get_access_count(); join_cnt++)
   {
     const Item* join_items[ndb_pushed_join::MAX_LINKED_KEYS+1];
-    const AQP::Table_access* join_parent= NULL;
+    const AQP::Table_access* join_parent;
 
     const AQP::Table_access* const join_tab= plan.get_table_access(join_cnt);
+    const NdbQueryOperationDef* query_op= NULL;
 
     if (join_tab->get_table()->file->ht != ht)
     {
@@ -758,7 +992,7 @@ ha_ndbcluster::make_pushed_join(AQP::Joi
       DBUG_PRINT("info", ("Table %d has user defined partioning, not pushable", join_cnt));
       continue;
     }
-    if (!field_ref_is_join_pushable(plan, join_root, pushed_scope, join_tab, join_items, join_parent))
+    if (!context.field_ref_is_join_pushable(join_tab, join_items, join_parent))
     {
       DBUG_PRINT("info", ("Table %d not EQ_REF-joined, not pushable", join_cnt));
       continue;
@@ -793,7 +1027,7 @@ ha_ndbcluster::make_pushed_join(AQP::Joi
           DBUG_PRINT("info", ("Root operation is 'primary-key-lookup'"));
           DBUG_ASSERT(join_root->get_index_no() == 
                       static_cast<int>(table->s->primary_key));
-          operation_defs[0]= builder.readTuple(m_table, root_key);
+          query_op= builder.readTuple(m_table, root_key);
         }
         else
         {
@@ -801,7 +1035,7 @@ ha_ndbcluster::make_pushed_join(AQP::Joi
           const NdbDictionary::Index* index 
             = m_index[join_root->get_index_no()].unique_index;
           DBUG_ASSERT(index);
-          operation_defs[0]= builder.readTuple(index, m_table, root_key);
+          query_op= builder.readTuple(index, m_table, root_key);
         }
       }
       /**
@@ -822,49 +1056,29 @@ ha_ndbcluster::make_pushed_join(AQP::Joi
                             m_index[join_root->get_index_no()].index->getName()));
 
         // Bounds will be generated and supplied during execute
-        operation_defs[0]= 
-          builder.scanIndex(m_index[join_root->get_index_no()].index, m_table);
+        query_op= builder.scanIndex(m_index[join_root->get_index_no()].index, m_table);
       }
       else if (access_type == AQP::AT_TABLE_SCAN) 
       {
         DBUG_PRINT("info", ("Root operation is 'table scan'"));
-        operation_defs[0]= builder.scanTable(m_table);
+        query_op= builder.scanTable(m_table);
       }
       else
       {
         DBUG_ASSERT(false);
       }
 
-//    DBUG_ASSERT(operation_defs[0]);
-      if (unlikely(!operation_defs[0]))
+//    DBUG_ASSERT(query_op);
+      if (unlikely(!query_op))
         ERR_RETURN(builder.getNdbError());
 
+      context.add_pushed(join_root, NULL, query_op);
       push_cnt= 1;
-    } // End: 'define parent'
+    } // End: 'define root'
 
     DBUG_PRINT("info", ("Appending child, join_cnt:%d, key_parts:%d", push_cnt, 
                         join_tab->get_no_of_key_fields()));
 
-    // Locate the  common parent operation for all 'ref.key_parts'
-    // Key may refer any of the preceeding parent tables
-    const NdbQueryOperationDef* parent_op= NULL;
-    if (join_parent != NULL)
-    {
-      int op_ix= 0;
-      for (uint i= join_root->get_access_no(); i < join_cnt; i++)
-      {
-        const AQP::Table_access* const p= plan.get_table_access(i);
-        if (join_parent == p)
-        {
-          parent_op= operation_defs[op_ix];
-          break;
-        }
-        if (pushed_scope.is_set(p->get_access_no()))
-          op_ix++;
-      }
-      DBUG_ASSERT(parent_op != NULL);
-    }
-
     KEY *key= &handler->table->key_info[join_tab->get_index_no()];
     KEY_PART_INFO *key_part= key->key_part;
     DBUG_ASSERT(join_tab->get_no_of_key_fields() == key->key_parts);
@@ -873,10 +1087,13 @@ ha_ndbcluster::make_pushed_join(AQP::Joi
     uint map[ndb_pushed_join::MAX_LINKED_KEYS+1];
     build_key_map(handler->m_table, handler->m_index[join_tab->get_index_no()], key, map);
 
+    ndb_table_access_map parent_map(join_parent);
+
     for (uint i= 0; i < key->key_parts; i++, key_part++)
     {
       const Item* item= join_items[i];
       linked_key[map[i]]= NULL;
+      DBUG_ASSERT(item->const_item() == item->const_during_execution());
       if (item->const_item())
       {
         // Items constant value is already propagated to Field defining
@@ -892,22 +1109,30 @@ ha_ndbcluster::make_pushed_join(AQP::Joi
       else
       {
         DBUG_ASSERT(item->type() == Item::FIELD_ITEM);
-        DBUG_ASSERT(join_parent >= 0);
         const Item_field* field_item= static_cast<const Item_field*>(item);
+        ndb_table_access_map used_table(field_item->used_tables());
 
-        const AQP::Table_access* const referred_table= 
-          plan.get_referred_table_access(field_item,join_root);
-        if (referred_table == join_parent)
+        if (context.join_scope().contain(used_table))
         {
+          const AQP::Table_access* const referred_table=
+            parent_map == used_table
+              ? join_parent
+              : context.get_referred_table_access(used_table);
+
+          // Locate the parent operation for this 'join_items[]'.
+          // May refer any of the preceeding parent tables
+          const NdbQueryOperationDef* parent_op= context.get_query_operation(referred_table);
+          DBUG_ASSERT(parent_op != NULL);
+
           // TODO use field_index ??
           linked_key[map[i]]=
               builder.linkedValue(parent_op, get_referred_field_name(field_item));
         }
         else
         {
+          DBUG_ASSERT(context.const_scope().contain(used_table));
           // Outside scope of join plan, Handle as parameter as its value
           // will be known when we are ready to execute this query.
-          DBUG_ASSERT (referred_table==NULL);
           if (unlikely(fld_refs >= ndb_pushed_join::MAX_REFERRED_FIELDS))
           {
             DBUG_PRINT("info", ("Too many Field refs ( >= MAX_REFERRED_FIELDS) encountered"));
@@ -926,7 +1151,7 @@ ha_ndbcluster::make_pushed_join(AQP::Joi
     // Link on primary key or an unique index
     if (join_tab->get_access_type() == AQP::AT_PRIMARY_KEY)
     {
-      operation_defs[push_cnt]= builder.readTuple(table, linked_key);
+      query_op= builder.readTuple(table, linked_key);
     }
     else
     {
@@ -934,18 +1159,15 @@ ha_ndbcluster::make_pushed_join(AQP::Joi
       const NdbDictionary::Index* index
         = handler->m_index[join_tab->get_index_no()].unique_index;
       DBUG_ASSERT(index != NULL);
-      operation_defs[push_cnt]= builder.readTuple(index, table, linked_key);
+      query_op= builder.readTuple(index, table, linked_key);
     }
 
-//  DBUG_ASSERT(operation_defs[push_cnt]);
-    if (unlikely(!operation_defs[push_cnt]))
+//  DBUG_ASSERT(query_op);
+    if (unlikely(!query_op))
       ERR_RETURN(builder.getNdbError());
 
-    pushed_scope.set_bit(join_tab->get_access_no());
+    context.add_pushed(join_tab, join_parent, query_op);
     push_cnt++;
-
-    if (push_cnt >= ndb_pushed_join::MAX_PUSHED_OPERATIONS)
-      break;
   } // for (uint join_cnt= 1; join_cnt<plan.get_access_count(); join_cnt++)
 
   if (push_cnt < 2)
@@ -967,7 +1189,7 @@ ha_ndbcluster::make_pushed_join(AQP::Joi
   m_thd_ndb->m_query_defs = list_item;
   
   DBUG_PRINT("info", ("Created pushed join with %d child operations", push_cnt-1));
-  m_pushed_join= new ndb_pushed_join(plan, pushed_scope, fld_refs, referred_fields, query_def);
+  m_pushed_join= new ndb_pushed_join(context.plan(), context.join_scope(), fld_refs, referred_fields, query_def);
 
   for (uint i = 0; i < push_cnt; i++)
   {

=== modified file 'storage/ndb/src/kernel/blocks/dbspj/Dbspj.hpp'
--- a/storage/ndb/src/kernel/blocks/dbspj/Dbspj.hpp	2010-05-20 11:18:08 +0000
+++ b/storage/ndb/src/kernel/blocks/dbspj/Dbspj.hpp	2010-06-16 10:40:24 +0000
@@ -345,7 +345,7 @@ public:
                              const QueryNode*, const QueryNodeParameters*);
 
     /**
-     * This function is called aftger build, but before start
+     * This function is called after build, but before start
      *   it's allowed to block (i.e send signals)
      *   and should if so increase request::m_outstanding
      */
@@ -985,7 +985,8 @@ private:
   Uint32 expandL(Uint32 & ptrI, Local_pattern_store&, const RowPtr&);
   Uint32 expand(Uint32 & ptrI, DABuffer& pattern, Uint32 len,
                 DABuffer & param, Uint32 cnt);
-  Uint32 expand(Local_pattern_store& dst, DABuffer& pattern, Uint32 len,
+  Uint32 expand(Local_pattern_store& dst, Ptr<TreeNode> treeNodePtr,
+                DABuffer & pattern, Uint32 len,
                 DABuffer & param, Uint32 cnt);
   Uint32 parseDA(Build_context&, Ptr<Request>, Ptr<TreeNode>,
                  DABuffer tree, Uint32 treeBits,

=== modified file 'storage/ndb/src/kernel/blocks/dbspj/DbspjMain.cpp'
--- a/storage/ndb/src/kernel/blocks/dbspj/DbspjMain.cpp	2010-05-20 12:12:15 +0000
+++ b/storage/ndb/src/kernel/blocks/dbspj/DbspjMain.cpp	2010-06-16 12:09:09 +0000
@@ -910,6 +910,29 @@ Dbspj::build(Build_context& ctx,
   }
   requestPtr.p->m_node_cnt = ctx.m_cnt;
 
+  /**
+   * Init ROW_BUFFERS for those TreeNodes requiring either 
+   * T_ROW_BUFFER or T_ROW_BUFFER_MAP.
+   */
+  if (requestPtr.p->m_bits & Request::RT_ROW_BUFFERS)
+  {
+    Ptr<TreeNode> treeNodePtr;
+    Local_TreeNode_list list(m_treenode_pool, requestPtr.p->m_nodes);
+    for (list.first(treeNodePtr); !treeNodePtr.isNull(); list.next(treeNodePtr))
+    {
+      if (treeNodePtr.p->m_bits & TreeNode::T_ROW_BUFFER_MAP)
+      {
+        jam();
+        treeNodePtr.p->m_row_map.init();
+      }
+      else if (treeNodePtr.p->m_bits & TreeNode::T_ROW_BUFFER)
+      {
+        jam();
+        treeNodePtr.p->m_row_list.init();
+      }
+    }
+  }
+
   if (ctx.m_scan_cnt > 1)
   {
     jam();
@@ -942,11 +965,6 @@ Dbspj::build(Build_context& ctx,
       list.remove();
     }
   }
-  
-//#define JONAS_TESTING_ROW_BUFFERING
-#ifdef JONAS_TESTING_ROW_BUFFERING
-  requestPtr.p->m_bits |= Request::RT_VAR_ALLOC;
-#endif
 
   return 0;
 
@@ -1809,7 +1827,7 @@ Dbspj::execTRANSID_AI(Signal* signal)
   struct RowPtr row;
   row.m_type = RowPtr::RT_SECTION;
   row.m_src_node_ptrI = treeNodePtr.i;
-  row.m_row_data.m_section.m_header = (RowPtr::Header*)tmp;
+  row.m_row_data.m_section.m_header = header;
   row.m_row_data.m_section.m_dataPtr.assign(dataPtr);
   Uint32 rootStreamId = 0;
 
@@ -1902,12 +1920,13 @@ Dbspj::setupRowPtr(Ptr<TreeNode> treeNod
 {
   Uint32 linklen = (treeNodePtr.p->m_bits & TreeNode::T_ROW_BUFFER_MAP)?
     0 : 2;
+  const RowPtr::Header * headptr = (RowPtr::Header*)(src + linklen);
+  Uint32 headlen = 1 + headptr->m_len;
 
   row.m_type = RowPtr::RT_LINEAR;
   row.m_row_data.m_linear.m_row_ref = ref;
-  row.m_row_data.m_linear.m_header = (RowPtr::Header*)(src + linklen);
-  row.m_row_data.m_linear.m_data = src + linklen + 
-    row.m_row_data.m_linear.m_header->m_len;
+  row.m_row_data.m_linear.m_header = headptr;
+  row.m_row_data.m_linear.m_data = (Uint32*)headptr + headlen;
 }
 
 void
@@ -2028,12 +2047,12 @@ Dbspj::next(Ptr<Request> requestPtr, Ptr
   if (iter.m_ref.m_allocator == 0)
   {
     jam();
-    iter.m_row_ptr = get_row_ptr_var(start.m_ref);
+    iter.m_row_ptr = get_row_ptr_stack(start.m_ref);
   }
   else
   {
     jam();
-    iter.m_row_ptr = get_row_ptr_stack(start.m_ref);
+    iter.m_row_ptr = get_row_ptr_var(start.m_ref);
   }
   return next(iter);
 }
@@ -3960,7 +3979,7 @@ Dbspj::parseScanIndex(Build_context& ctx
         /**
          * Expand pattern into a new pattern (with linked values)
          */
-        err = expand(pattern, tree, len, param, cnt);
+        err = expand(pattern, treeNodePtr, tree, len, param, cnt);
         treeNodePtr.p->m_bits |= TreeNode::T_PRUNE_PATTERN;
         c_Counters.incr_counter(CI_PRUNNED_RANGE_SCANS_RECEIVED, 1);
       }
@@ -5165,7 +5184,7 @@ Dbspj::appendFromParent(Uint32 & dst, Lo
     if (allocator == 0)
     {
       jam();
-      mapptr = get_row_ptr_var(ref);
+      mapptr = get_row_ptr_stack(ref);
     }
     else
     {
@@ -5517,7 +5536,8 @@ Dbspj::expand(Uint32 & ptrI, DABuffer& p
       }
       ptr += val;
       break;
-    case QueryPattern::P_COL: // (linked) COL's not expected here
+    case QueryPattern::P_COL:    // (linked) COL's not expected here
+    case QueryPattern::P_PARENT: // Prefix to P_COL
     case QueryPattern::P_ATTRINFO:
     case QueryPattern::P_UNQ_PK:
     default:
@@ -5546,23 +5566,13 @@ error:
 }
 
 Uint32
-Dbspj::expand(Local_pattern_store& dst, DABuffer& pattern, Uint32 len,
+Dbspj::expand(Local_pattern_store& dst, Ptr<TreeNode> treeNodePtr,
+              DABuffer& pattern, Uint32 len,
               DABuffer& param, Uint32 paramCnt)
 {
   /**
    * TODO handle error
    */
-  /**
-   * Optimization: If no params in key, const + linked col are 
-   * transfered unaltered to the pattern to be parsed when the
-   * parent source of the linked columns are available.
-   */
-  if (paramCnt == 0)
-  {
-    jam();
-    return appendToPattern(dst, pattern, len);
-  }
-
   Uint32 err;
   Uint32 tmp[1+MAX_ATTRIBUTES_IN_TABLE];
   struct RowPtr::Linear row;
@@ -5605,6 +5615,28 @@ Dbspj::expand(Local_pattern_store& dst, 
       ndbrequire(false);
 #endif
       break;
+    case QueryPattern::P_PARENT: // Prefix to P_COL
+    {
+      jam();
+      err = appendToPattern(dst, pattern, 1);
+
+      // Locate requested grandparent and request it to
+      // T_ROW_BUFFER its result rows
+      Ptr<TreeNode> parentPtr;
+      m_treenode_pool.getPtr(parentPtr, treeNodePtr.p->m_parentPtrI);
+      while (val--)
+      {
+        jam();
+        ndbassert(parentPtr.p->m_parentPtrI != RNIL);
+        m_treenode_pool.getPtr(parentPtr, parentPtr.p->m_parentPtrI);
+        parentPtr.p->m_bits |= TreeNode::T_ROW_BUFFER;
+        parentPtr.p->m_bits |= TreeNode::T_ROW_BUFFER_MAP;
+      }
+      Ptr<Request> requestPtr;
+      m_request_pool.getPtr(requestPtr, treeNodePtr.p->m_requestPtrI);
+      requestPtr.p->m_bits |= Request::RT_ROW_BUFFERS;
+      break;
+   }
    default:
       jam();
       err = DbspjErr::InvalidPattern;
@@ -5660,6 +5692,14 @@ Dbspj::parseDA(Build_context& ctx,
 
       err = 0;
       
+      if (unlikely(cnt!=1))
+      {
+        /**
+         * Only a single parent supported for now, i.e only trees
+         */
+        DEBUG_CRASH();
+      }
+
       for (Uint32 i = 0; i<cnt; i++)
       {
         DEBUG("adding " << dst[i] << " as parent");
@@ -5673,14 +5713,7 @@ Dbspj::parseDA(Build_context& ctx,
           break;
         }
         parentPtr.p->m_bits &= ~(Uint32)TreeNode::T_LEAF;
-
-        if (i == 0)
-        {
-          /**
-           * Save parent (only 0 for now, i.e only trees)
-           */
-          treeNodePtr.p->m_parentPtrI = parentPtr.i;
-        }
+        treeNodePtr.p->m_parentPtrI = parentPtr.i;
       }
 
       if (unlikely(err != 0))
@@ -5730,7 +5763,7 @@ Dbspj::parseDA(Build_context& ctx,
         /**
          * Expand pattern into a new pattern (with linked values)
          */
-        err = expand(pattern, tree, len, param, cnt);
+        err = expand(pattern, treeNodePtr, tree, len, param, cnt);
 
         /**
          * This node constructs a new key for each send
@@ -5857,7 +5890,7 @@ Dbspj::parseDA(Build_context& ctx,
             LocalArenaPoolImpl pool(requestPtr.p->m_arena, 
                                     m_dependency_map_pool);
             Local_pattern_store pattern(pool,treeNodePtr.p->m_attrParamPattern);
-            err = expand(pattern, tree, len_pattern, param, cnt);
+            err = expand(pattern, treeNodePtr, tree, len_pattern, param, cnt);
             
             /**
              * This node constructs a new attr-info for each send
@@ -6048,30 +6081,6 @@ Dbspj::parseDA(Build_context& ctx,
       treeNodePtr.p->m_send.m_attrInfoPtrI = attrInfoPtrI;
     } // if (((treeBits & mask) | (paramBits & DABits::PI_ATTR_LIST)) != 0)
 
-#ifdef JONAS_TESTING_ROW_BUFFERING
-    // TODO: test-only!
-    treeNodePtr.p->m_bits |= TreeNode::T_ROW_BUFFER;
-    treeNodePtr.p->m_bits |= TreeNode::T_ROW_BUFFER_MAP;
-    treeNodePtr.p->m_bits |= TreeNode::T_MULTI_SCAN;
-#endif
-
-    if (treeNodePtr.p->m_bits & TreeNode::T_ROW_BUFFER)
-    {
-      jam();
-      requestPtr.p->m_bits |= Request::RT_ROW_BUFFERS;
-
-      if (treeNodePtr.p->m_bits & TreeNode::T_ROW_BUFFER_MAP)
-      {
-        jam();
-        treeNodePtr.p->m_row_map.init();
-      }
-      else
-      {
-        jam();
-        treeNodePtr.p->m_row_list.init();
-      }
-    }
-
     return 0;
   } while (0);
 

=== modified file 'storage/ndb/src/ndbapi/NdbQueryBuilder.cpp'
--- a/storage/ndb/src/ndbapi/NdbQueryBuilder.cpp	2010-05-20 06:36:59 +0000
+++ b/storage/ndb/src/ndbapi/NdbQueryBuilder.cpp	2010-06-16 10:45:31 +0000
@@ -182,6 +182,9 @@ protected:
   virtual const NdbQueryLookupOperationDef& getInterface() const
   { return m_interface; }
 
+  // Append pattern for creating lookup key to serialized code 
+  Uint32 appendKeyPattern(Uint32Buffer& serializedDef) const;
+
   virtual bool isScanOperation() const
   { return false; }
 
@@ -1590,8 +1593,12 @@ void
 NdbQueryOperationDefImpl::removeChild(const NdbQueryOperationDefImpl* childOp)
 {
   for (unsigned i=0; i<m_children.size(); ++i)
-  { if (m_children[i] == childOp)
+  {
+    if (m_children[i] == childOp)
+    {
       m_children.erase(i);
+      return;
+    }
   }
 }
 
@@ -1599,8 +1606,22 @@ bool
 NdbQueryOperationDefImpl::isChildOf(const NdbQueryOperationDefImpl* parentOp) const
 {
   for (Uint32 i=0; i<m_parents.size(); ++i)
-  { if (m_parents[i] == parentOp || m_parents[i]->isChildOf(parentOp))
+  { if (this->m_parents[i] == parentOp)
+    {
+#ifndef NDEBUG
+      // Assert that parentOp also refer 'this' as a child.
+      for (Uint32 j=0; j<parentOp->getNoOfChildOperations(); j++)
+      { if (&parentOp->getChildOperation(j) == this)
+          return true;
+      }
+      assert(false);
+#endif
+      return true;
+    }
+    else if (m_parents[i]->isChildOf(parentOp))
+    {
       return true;
+    }
   }
   return false;
 }
@@ -1608,40 +1629,25 @@ NdbQueryOperationDefImpl::isChildOf(cons
 int
 NdbQueryOperationDefImpl::linkWithParent(NdbQueryOperationDefImpl* parentOp)
 {
-  for (Uint32 i=0; i<m_parents.size(); ++i)
-  { if (m_parents[i] == parentOp)
-    { // Assert that parent already refer 'this' as a child.
-      for (Uint32 j=0; j<parentOp->getNoOfChildOperations(); j++)
-      { if (&parentOp->getChildOperation(j) == this)
-          return 0;
-      }
-      assert(false);
-      return 0;
-    }
+  if (this->isChildOf(parentOp))
+  {
+    // There should only be a single parent/child relationship registered.
+    return 0;
   }
 
   assert (m_parents.size() <= 1);
-  if (unlikely(m_parents.size() == 1))
+  if (m_parents.size() > 0)
   {
     /**
-     * Parent merging current disabled 
-     *  - Will also need additional support in SPJ block
+     * Multiple parental relationship not allowed.
+     * It is likely that the conflict is due to one of the
+     * parent actually being a grand parent.
+     * This can be resolved if existing parent actually was a grandparent.
+     * Then register new parentOp as the real parrent.
      */
-    return QRY_MULTIPLE_PARENTS;
-
-    /**
-     * Multiple parents not allowed.
-     * Resolve this by finding the closest related (grand)parent among 
-     * the two parents. This is calculated as the parent having the 
-     * other parent as grand parent.
-     */
-    if (m_parents[0]->isChildOf(parentOp))
-    { // parentOp is grandparent of m_parent[0], don't interested in it
-      return 0; // Accept existing m_parent[0] as my parent.
-    }
-    else if (parentOp->isChildOf(m_parents[0]))
-    { // Remove existing grandparent linkage being replaced by .
-      parentOp->removeChild(this);
+    if (parentOp->isChildOf(m_parents[0]))
+    { // Remove existing grandparent linkage being replaced by parentOp.
+      m_parents[0]->removeChild(this);
       m_parents.erase(0);
     }
     else
@@ -1752,18 +1758,17 @@ NdbQueryOperationDefImpl::appendParentLi
   parentSeq.finish();
 }
 
-static Uint32
-appendKeyPattern(Uint32Buffer& serializedDef,
-                 const NdbQueryOperandImpl* const *keys)
+Uint32
+NdbQueryLookupOperationDefImpl::appendKeyPattern(Uint32Buffer& serializedDef) const
 {
   Uint32 appendedPattern = 0;
-  if (keys[0]!=NULL)
+  if (m_keys[0]!=NULL)
   {
     Uint32 startPos = serializedDef.getSize();
     serializedDef.append(0);     // Grab first word for length field, updated at end
     int paramCnt = 0;
     int keyNo = 0;
-    const NdbQueryOperandImpl* key = keys[0];
+    const NdbQueryOperandImpl* key = m_keys[0];
     do
     {
       switch(key->getKind()){
@@ -1771,6 +1776,18 @@ appendKeyPattern(Uint32Buffer& serialize
       {
         appendedPattern |= DABits::NI_KEY_LINKED;
         const NdbLinkedOperandImpl& linkedOp = *static_cast<const NdbLinkedOperandImpl*>(key);
+        const NdbQueryOperationDefImpl* parent = &getParentOperation(0);
+        uint32 levels = 0;
+        while (parent != &linkedOp.getParentOperation())
+        {
+          assert(parent->getNoOfParentOperations() > 0);
+          parent = &parent->getParentOperation(0);
+          levels++;
+        }
+        if (levels > 0)
+        {
+          serializedDef.append(QueryPattern::parent(levels));
+        }
         serializedDef.append(QueryPattern::col(linkedOp.getLinkedColumnIx()));
         break;
       }
@@ -1795,7 +1812,7 @@ appendKeyPattern(Uint32Buffer& serialize
       default:
         assert(false);
       }
-      key = keys[++keyNo];
+      key = m_keys[++keyNo];
     } while (key!=NULL);
 
     // Set total length of key pattern.
@@ -1804,7 +1821,7 @@ appendKeyPattern(Uint32Buffer& serialize
   }
 
   return appendedPattern;
-} // appendKeyPattern
+} // NdbQueryLookupOperationDefImpl::appendKeyPattern
 
 int
 NdbQueryPKLookupOperationDefImpl
@@ -1836,7 +1853,7 @@ NdbQueryPKLookupOperationDefImpl
 
   // Part2: Append m_keys[] values specifying lookup key.
   if (getQueryOperationIx() > 0) {
-    requestInfo |= appendKeyPattern(serializedDef, m_keys);
+    requestInfo |= appendKeyPattern(serializedDef);
   }
 
   /* Add the projection that should be send to the SPJ block such that 
@@ -1912,7 +1929,7 @@ NdbQueryIndexOperationDefImpl
 
     // Part2: m_keys[] are the keys to be used for index
     if (getQueryOperationIx() > 0) {  // Root operation key is in KEYINFO 
-      requestInfo |= appendKeyPattern(serializedDef, m_keys);
+      requestInfo |= appendKeyPattern(serializedDef);
     }
 
     /* Basetable is executed as child operation of index:


Attachment: [text/bzr-bundle] bzr/ole.john.aske@sun.com-20100621093355-y8tgjj8jtyrjte8h.bundle
Thread
bzr push into mysql-5.1-telco-7.0-spj branch (ole.john.aske:3172 to 3178) Ole John Aske21 Jun