From: Ole John Aske Date: October 11 2010 12:11pm Subject: bzr push into mysql-5.1-telco-7.0-spj-scan-vs-scan branch (ole.john.aske:3308 to 3309) List-Archive: http://lists.mysql.com/commits/120495 Message-Id: <20101011121158.2ED6621D@fimafeng09.norway.sun.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============3704211163717810187==" --===============3704211163717810187== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline 3309 Ole John Aske 2010-10-11 spj-svs: Added missing handling NULL values in pruned scan keys. Pruned keys may be constructed from linkedValues which may contain a NULL value. As a pruned key implies equality on the fields in the key, a NULL value in the prune key can never match any existing tuple on the datanodes. We can therefore eliminate any pruned SCANREQ with NULL value in the prune key. In addition to the fix above, this commit also adds: - Checking for errors returns from ::expand(). - add ndbrequire(!hasNull) to those places where we do not expect (and does not handle) NULL values in the key produced by ::expand(). - Some more DEBUG output for debugging pruned scans. - Moves the 'prune pattern' (SI_PRUNE_PATTERN) to be the last part of the 'optional' part of a Qserialized ueryTree node - Required as 'parent' relationship for the node should be established by ::parseDA() before we could ::expand() the pattern for the pruned key. modified: mysql-test/suite/ndb/r/ndb_join_pushdown.result mysql-test/suite/ndb/t/ndb_join_pushdown.test storage/ndb/src/kernel/blocks/dbspj/Dbspj.hpp storage/ndb/src/kernel/blocks/dbspj/DbspjMain.cpp storage/ndb/src/ndbapi/NdbQueryBuilder.cpp 3308 Ole John Aske 2010-10-11 spj-svs: Fixed an undefined memory read Fixed a situation where an empty 'm_spjProjection' will cause NdbQueryOperationDefImpl::appendChildProjection() to set the QueryTree flag 'NI_LINKED_ATTR' without appending a projection list to the serialized query tree. This fix will ensure that a serialized m_spjProjection list with size==0 will be included in these cases. No testcase as I can't think of any deterministic ways to make a testcase for an undefined memory read.... modified: storage/ndb/src/ndbapi/NdbQueryBuilder.cpp === modified file 'mysql-test/suite/ndb/r/ndb_join_pushdown.result' --- a/mysql-test/suite/ndb/r/ndb_join_pushdown.result 2010-10-11 09:48:36 +0000 +++ b/mysql-test/suite/ndb/r/ndb_join_pushdown.result 2010-10-11 12:11:05 +0000 @@ -3701,8 +3701,8 @@ pk u a b pk u a b pk u a b drop table t1; create table t1( d int not null, -e int not null, -f int not null, +e int null, +f int null, a int not null, b int not null, c int not null, @@ -3883,8 +3883,32 @@ select straight_join * from t1 x, t1 y w id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE x ALL PRIMARY NULL NULL NULL 8 Parent of 2 pushed join@1 1 SIMPLE y ref ix1 ix1 4 test.x.e 1 Child of pushed join@1; Using where +insert into t1(a,b,c,d,e,f) values +(8, 9, 0, 1, NULL, 3), +(9, 9, 0, 1, 2, NULL); +alter table t1 partition by key (b); +select straight_join * from t1 x, t1 y where y.a=x.d and y.b=x.e; +d e f a b c d e f a b c +1 2 3 1 2 3 1 2 3 1 2 3 +1 2 3 1 2 3 1 2 3 1 2 4 +1 2 3 1 2 4 1 2 3 1 2 3 +1 2 3 1 2 4 1 2 3 1 2 4 +1 2 3 2 3 4 1 2 3 1 2 3 +1 2 3 2 3 4 1 2 3 1 2 4 +1 2 3 3 4 5 1 2 3 1 2 3 +1 2 3 3 4 5 1 2 3 1 2 4 +1 2 3 4 5 6 1 2 3 1 2 3 +1 2 3 4 5 6 1 2 3 1 2 4 +1 2 3 5 6 7 1 2 3 1 2 3 +1 2 3 5 6 7 1 2 3 1 2 4 +1 2 3 6 7 8 1 2 3 1 2 3 +1 2 3 6 7 8 1 2 3 1 2 4 +1 2 3 7 8 9 1 2 3 1 2 3 +1 2 3 7 8 9 1 2 3 1 2 4 +1 2 NULL 9 9 0 1 2 3 1 2 3 +1 2 NULL 9 9 0 1 2 3 1 2 4 pruned -12 +14 const_pruned 6 drop table t1; @@ -4005,13 +4029,13 @@ and spj_counts_at_end.counter_name <> 'L and spj_counts_at_end.counter_name <> 'SCAN_BATCHES_RETURNED'; counter_name spj_counts_at_end.val - spj_counts_at_startup.val CONST_PRUNED_RANGE_SCANS_RECEIVED 6 -LOCAL_TABLE_SCANS_SENT 192 -PRUNED_RANGE_SCANS_RECEIVED 14 +LOCAL_TABLE_SCANS_SENT 194 +PRUNED_RANGE_SCANS_RECEIVED 16 RANGE_SCANS_RECEIVED 195 READS_NOT_FOUND 403 READS_RECEIVED 61 -SCAN_ROWS_RETURNED 63563 -TABLE_SCANS_RECEIVED 192 +SCAN_ROWS_RETURNED 63591 +TABLE_SCANS_RECEIVED 194 select sum(spj_counts_at_end.val - spj_counts_at_startup.val) as 'LOCAL+REMOTE READS_SENT' from spj_counts_at_end, spj_counts_at_startup where spj_counts_at_end.counter_name = spj_counts_at_startup.counter_name @@ -4022,15 +4046,15 @@ LOCAL+REMOTE READS_SENT drop table spj_counts_at_startup; drop table spj_counts_at_end; scan_count -1985 +1990 pruned_scan_count 7 sorted_scan_count 44 pushed_queries_defined -334 +335 pushed_queries_dropped 11 pushed_queries_executed -258 +259 set ndb_join_pushdown = @save_ndb_join_pushdown; === modified file 'mysql-test/suite/ndb/t/ndb_join_pushdown.test' --- a/mysql-test/suite/ndb/t/ndb_join_pushdown.test 2010-10-11 09:48:36 +0000 +++ b/mysql-test/suite/ndb/t/ndb_join_pushdown.test 2010-10-11 12:11:05 +0000 @@ -2708,8 +2708,8 @@ drop table t1; # Test pruned index scan: create table t1( d int not null, - e int not null, - f int not null, + e int null, + f int null, a int not null, b int not null, c int not null, @@ -2784,6 +2784,17 @@ create index ix1 on t1(b,d,a); explain select straight_join * from t1 x, t1 y where y.a=x.d and y.b=x.e; +########### +# Partition keys may evaluate to NULL-values: +########### +insert into t1(a,b,c,d,e,f) values + (8, 9, 0, 1, NULL, 3), + (9, 9, 0, 1, 2, NULL); + +alter table t1 partition by key (b); +--sorted_result +select straight_join * from t1 x, t1 y where y.a=x.d and y.b=x.e; + # Verify pruned execution by comparing the NDB$INFO counters --disable_query_log --eval select sum(val) - $pruned_range AS pruned from ndbinfo.counters where block_name='DBSPJ' and counter_name='PRUNED_RANGE_SCANS_RECEIVED' === modified file 'storage/ndb/src/kernel/blocks/dbspj/Dbspj.hpp' --- a/storage/ndb/src/kernel/blocks/dbspj/Dbspj.hpp 2010-09-24 08:46:53 +0000 +++ b/storage/ndb/src/kernel/blocks/dbspj/Dbspj.hpp 2010-10-11 12:11:05 +0000 @@ -1067,8 +1067,8 @@ private: DABuffer & pattern, Uint32 len, DABuffer & param, Uint32 cnt); Uint32 parseDA(Build_context&, Ptr, Ptr, - DABuffer tree, Uint32 treeBits, - DABuffer param, Uint32 paramBits); + DABuffer & tree, Uint32 treeBits, + DABuffer & param, Uint32 paramBits); Uint32 createEmptySection(Uint32 & ptrI); === modified file 'storage/ndb/src/kernel/blocks/dbspj/DbspjMain.cpp' --- a/storage/ndb/src/kernel/blocks/dbspj/DbspjMain.cpp 2010-10-04 17:00:36 +0000 +++ b/storage/ndb/src/kernel/blocks/dbspj/DbspjMain.cpp 2010-10-11 12:11:05 +0000 @@ -3189,7 +3189,7 @@ Dbspj::lookup_parent_row(Signal* signal, { releaseSection(ptrI); } - return; // Bailout, KEYREQ would have returned KEYREF(626) + return; // Bailout, KEYREQ would have returned KEYREF(626) anyway } else // isLookup() { @@ -3204,7 +3204,8 @@ Dbspj::lookup_parent_row(Signal* signal, */ jam(); } - } + } // keyIsNull + /** * NOTE: * The logic below contradicts 'keyIsNull' logic above and should @@ -4319,6 +4320,11 @@ Dbspj::parseScanIndex(Build_context& ctx data.m_frags_outstanding = 0; data.m_frags_not_complete = 0; + err = parseDA(ctx, requestPtr, treeNodePtr, + tree, treeBits, param, paramBits); + if (unlikely(err != 0)) + break; + if (treeBits & Node::SI_PRUNE_PATTERN) { Uint32 len_cnt = * tree.ptr ++; @@ -4332,6 +4338,7 @@ Dbspj::parseScanIndex(Build_context& ctx if (treeBits & Node::SI_PRUNE_LINKED) { jam(); + DEBUG("LINKED-PRUNE PATTERN w/ " << cnt << " PARAM values"); data.m_prunePattern.init(); Local_pattern_store pattern(pool, data.m_prunePattern); @@ -4340,12 +4347,17 @@ Dbspj::parseScanIndex(Build_context& ctx * Expand pattern into a new pattern (with linked values) */ err = expand(pattern, treeNodePtr, tree, len, param, cnt); + if (unlikely(err != 0)) + break; + treeNodePtr.p->m_bits |= TreeNode::T_PRUNE_PATTERN; c_Counters.incr_counter(CI_PRUNED_RANGE_SCANS_RECEIVED, 1); } else { jam(); + DEBUG("FIXED-PRUNE w/ " << cnt << " PARAM values"); + /** * Expand pattern directly into * This means a "fixed" pruning from here on @@ -4354,9 +4366,20 @@ Dbspj::parseScanIndex(Build_context& ctx Uint32 prunePtrI = RNIL; bool hasNull; err = expand(prunePtrI, tree, len, param, cnt, hasNull); - data.m_constPrunePtrI = prunePtrI; + if (unlikely(err != 0)) + break; -// ndbrequire(!hasNull); /* todo: can we take advantage of NULLs in range scan? */ + if (unlikely(hasNull)) + { + /* API should have elliminated requests w/ const-NULL keys */ + jam(); + DEBUG("BEWARE: T_CONST_PRUNE-key contain NULL values"); +// treeNodePtr.p->m_bits |= TreeNode::T_NULL_PRUNE; +// break; + ndbrequire(false); + } + ndbrequire(prunePtrI != RNIL); /* todo: can we allow / take advantage of NULLs in range scan? */ + data.m_constPrunePtrI = prunePtrI; /** * We may not compute the partition for the hash-key here @@ -4365,7 +4388,7 @@ Dbspj::parseScanIndex(Build_context& ctx treeNodePtr.p->m_bits |= TreeNode::T_CONST_PRUNE; c_Counters.incr_counter(CI_CONST_PRUNED_RANGE_SCANS_RECEIVED, 1); } - } + } //SI_PRUNE_PATTERN if ((treeNodePtr.p->m_bits & TreeNode::T_CONST_PRUNE) == 0 && ((treeBits & Node::SI_PARALLEL) || @@ -4375,8 +4398,7 @@ Dbspj::parseScanIndex(Build_context& ctx treeNodePtr.p->m_bits |= TreeNode::T_SCAN_PARALLEL; } - return parseDA(ctx, requestPtr, treeNodePtr, - tree, treeBits, param, paramBits); + return 0; } while(0); DEBUG_CRASH(); @@ -4659,7 +4681,19 @@ Dbspj::scanIndex_parent_row(Signal* sign DEBUG_CRASH(); break; } -// ndbrequire(!hasNull); + + if (unlikely(hasNull)) + { + jam(); + DEBUG("T_PRUNE_PATTERN-key contain NULL values"); + + // Ignore this request as 'NULL == ' will never give a match + if (pruneKeyPtrI != RNIL) + { + releaseSection(pruneKeyPtrI); + } + return; // Bailout, SCANREQ would have returned 0 rows anyway + } // TODO we need a different variant of computeHash here, // since pruneKeyPtrI does not contain full primary key @@ -4741,7 +4775,7 @@ Dbspj::scanIndex_parent_row(Signal* sign // Fixed key...fix later... ndbrequire(false); } -// ndbrequire(!hasNull); +// ndbrequire(!hasNull); // FIXME, can't ignore request as we already added it to keyPattern fragPtr.p->m_rangePtrI = ptrI; scanIndex_fixupBound(fragPtr, ptrI, rowRef.m_src_correlation); @@ -6296,8 +6330,8 @@ Uint32 Dbspj::parseDA(Build_context& ctx, Ptr requestPtr, Ptr treeNodePtr, - DABuffer tree, Uint32 treeBits, - DABuffer param, Uint32 paramBits) + DABuffer& tree, Uint32 treeBits, + DABuffer& param, Uint32 paramBits) { Uint32 err; Uint32 attrInfoPtrI = RNIL; @@ -6417,7 +6451,15 @@ Dbspj::parseDA(Build_context& ctx, bool hasNull; Uint32 keyInfoPtrI = RNIL; err = expand(keyInfoPtrI, tree, len, param, cnt, hasNull); -// ndbrequire(!hasNull); + if (unlikely(hasNull)) + { + /* API should have elliminated requests w/ const-NULL keys */ + jam(); + DEBUG("BEWARE: FIXED-key contain NULL values"); +// treeNodePtr.p->m_bits |= TreeNode::T_NULL_PRUNE; +// break; + ndbrequire(false); + } treeNodePtr.p->m_send.m_keyInfoPtrI = keyInfoPtrI; } @@ -6529,7 +6571,11 @@ Dbspj::parseDA(Build_context& ctx, m_dependency_map_pool); Local_pattern_store pattern(pool,treeNodePtr.p->m_attrParamPattern); err = expand(pattern, treeNodePtr, tree, len_pattern, param, cnt); - + if (unlikely(err)) + { + DEBUG_CRASH(); + break; + } /** * This node constructs a new attr-info for each send */ @@ -6544,6 +6590,11 @@ Dbspj::parseDA(Build_context& ctx, */ bool hasNull; err = expand(attrParamPtrI, tree, len_pattern, param, cnt, hasNull); + if (unlikely(err)) + { + DEBUG_CRASH(); + break; + } // ndbrequire(!hasNull); } } === modified file 'storage/ndb/src/ndbapi/NdbQueryBuilder.cpp' --- a/storage/ndb/src/ndbapi/NdbQueryBuilder.cpp 2010-10-11 09:55:24 +0000 +++ b/storage/ndb/src/ndbapi/NdbQueryBuilder.cpp 2010-10-11 12:11:05 +0000 @@ -2511,9 +2511,6 @@ NdbQueryScanOperationDefImpl::serialize( serializedDef.alloc(QN_ScanFragNode::NodeSize); Uint32 requestInfo = 0; - // Optional prefix part0: Pattern to creating a prune key for range scan - requestInfo |= appendPrunePattern(serializedDef); - // Optional part1: Make list of parent nodes. requestInfo |= appendParentList (serializedDef); @@ -2523,6 +2520,9 @@ NdbQueryScanOperationDefImpl::serialize( // Part3: Columns required by SPJ to instantiate descendant child operations. requestInfo |= appendChildProjection(serializedDef); + // Part4: Pattern to creating a prune key for range scan + requestInfo |= appendPrunePattern(serializedDef); + const Uint32 length = serializedDef.getSize() - startPos; if (unlikely(length > 0xFFFF)) { --===============3704211163717810187== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/ole.john.aske@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: ole.john.aske@stripped\ # t18b2uru9vxxowha # target_branch: file:///net/fimafeng09/export/home/tmp/oleja/mysql\ # /mysql-5.1-telco-7.0-spj-scan-scan/ # testament_sha1: 8d6923b11362b5b73e1266742d4bdf938b7c5d32 # timestamp: 2010-10-11 14:11:58 +0200 # source_branch: bzr+ssh://oaske@stripped/bzrroot/server\ # /mysql-5.1-telco-7.0-spj/ # base_revision_id: ole.john.aske@stripped\ # c6k1ptd3zvc3ipl8 # # Begin bundle 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 XckU4UJCjDPbeA== --===============3704211163717810187==--