Bug #37934 Partition pruning doesn't work for all queries in DBT2
Submitted: 7 Jul 2008 15:12 Modified: 14 Mar 2009 15:45
Reporter: Mikael Ronström Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Cluster: Cluster (NDB) storage engine Severity:S3 (Non-critical)
Version:5.1.23-ndb-6.3.12 OS:Any
Assigned to: Frazer Clement CPU Architecture:Any

[7 Jul 2008 15:12] Mikael Ronström
Description:
I ran DBT2 and set it up with tracing of signals and I could see that the
ndbd node that should have received no scans at all actually received about
1000 per second.

This happens on the order_line table and seems also to happen on the
customer table but only 5 per second here. A bit 

There are also queries that is correctly handled, in particular all
PK and UK lookups are correctly handled and also a part of the
scan queries.

I applied the patch from bug#36950 and the problem remains.

The output from EXPLAIN PARTITIONS always indicates that just one partition is to be used.

How to repeat:
Run DBT2 on at least a 4-node system, run with 1 warehouse,
2 of the ndbd nodes should now be idle but aren't
[8 Jul 2008 11:56] Mikael Ronström
I found the solution I think now:
read multi range involving scans ignore the partition pruning.
I added these lines below in ha_ndbcluster.cc in the method
read_multi_range_first and it seems to fix the issue.

 
       if (lm == NdbOperation::LM_Read)
          options.scan_flags|= NdbScanOperation::SF_KeyInfo;
        if (sorted)
          options.scan_flags|= NdbScanOperation::SF_OrderBy;

        options.parallel=parallelism;

/* Start of new code */
        /* Partition pruning */
        if (m_use_partition_pruning &&
            part_spec.start_part == part_spec.end_part)
        {
          options.partitionId = part_spec.start_part;
          options.optionsPresent |= NdbScanOperation::ScanOptions::SO_PARTITION_ID;
        }

/* End of new code */
        NdbOperation::GetValueSpec gets[2];
        if (table_share->primary_key == MAX_KEY)
          get_hidden_fields_scan(&options, gets);

        if (m_cond && m_cond->generate_scan_filter(&code, &options))
          ERR_RETURN(code.getNdbError());

        /* Define scan */
        NdbIndexScanOperation *scanOp= trans->scanIndex
          (m_index[active_index].ndb_record_key,
           m_ndb_record,
           lm,
           (uchar *)(table->read_set->bitmap),
           NULL, /* All bounds specified below */
           &options,
           sizeof(NdbScanOperation::ScanOptions));
[8 Jul 2008 11:58] Mikael Ronström
Here is a testcase as well:
The tricky part is how to verify that it uses partition pruning, currently
no test case for this.

-- source include/have_ndb.inc

--disable_query_log
set new=on;
--enable_query_log

--disable_warnings
drop table if exists t1;
--enable_warnings

#
# BUG37934:
# Partition pruning is broken for DBT2 table
#
CREATE TABLE t1 (
  ol_o_id int NOT NULL,
  ol_d_id int NOT NULL,
  ol_w_id int NOT NULL,
  ol_number int NOT NULL,
  ol_tmp int,
  PRIMARY KEY (ol_w_id, ol_d_id, ol_o_id, ol_number)
) ENGINE=NDB
PARTITION BY HASH (ol_w_id);

insert into t1 values (1,0,0,0,0),(1,0,0,1,0),(1,0,0,2,0),(1,1,0,0,0),(1,1,0,1,0);
insert into t1 values (2,0,0,0,0),(4,0,0,0,0),(4,0,0,1,0);

UPDATE t1 force index (primary) SET ol_tmp = 1
WHERE ol_o_id = 0 AND ol_w_id = 1 AND ol_d_id = 1;

SELECT * FROM t1 WHERE ol_w_id = 1 AND ol_d_id = 1 AND ol_o_id = 0;
SELECT SUM(ol_number) FROM t1 WHERE ol_o_id = 0 AND ol_w_id = 1 AND ol_d_id = 1;

drop table t1;

~                                                                                                             
~
[15 Oct 2008 16:46] Frazer Clement
Proposed patch for MRR scan partition pruning

Attachment: mrr_scan_part_prune.patch (text/x-patch), 3.73 KiB.

[15 Oct 2008 16:49] Frazer Clement
Modified patch (against 6.2)

The original set the partition id based on the partition of the first range.

Where multiple ranges exist, this resulted in incomplete results being returned (shown up by running MTR with ndb_partition_range).

Proposed patch checks all scannable ranges when first defining the scan, to determine whether it is correct to prune to one partition or not.

Perhaps this information is already available somewhere in the partitioning 'layer'?
[28 Jan 2009 9:08] Jonas Oreland
frazer: can you please get with an estimate on how much time it will take
  to impl. my suggestion instead.
  i.e to have the partition id *per bound*, and put the check if multiple
  partitions are specified *inside* ndbapi

/me gets increased nagging from mikael on this due to some new benchmark
[28 Jan 2009 12:12] Frazer Clement
Suggested fix : 
  - Modify bound-setting API to allow either partition id, or key parts (as passed to startTransaction()) to be optionally specified per-bound
  - PartitionId will be accepted for user-defined partitioned tables
  - For tables partitioned by key, key parts are required as the actual partition id is not generally known by the API.
  - When all supplied bounds have the same partition id (or partition hash from key parts), scan pruning is possible,
  - In future, the scan protocol API -> TC could be extended to support pruning even with >1 partition id per scan.  In the meantime, MySQLD/clients could sort bounds into sets where the same partition id is used.

Additional considerations
  - Where keyparts are provided, the API node can calculate the hash of the distribution key, but cannot directly map this to a partitionid.  The hash result (rather than the partitionid) can be sent in the scan request.  While there may be multiple hash results that map to a single partitionid, this cannot be known by the API, so it can only apply pruning if all ranges map to the same hash value.
  - Use of the term 'bound' is confusing as it is used to refer to 1) an upper or lower value for 1 column in a key, 2) an upper or lower value for all columns in a key, 3) Both upper and lower values for all columns in a key.  Perhaps 'range' should be used for 3), 'bound' used for 2) and 'column bound' used for 1)?
  - The existing IndexBound type has all members in a flat structure.  It may be easier to work with if it had 2 members - upper and lower, and they each had key_ptr, key_count and inclusive flags.
  - The existing IndexBound type does not explicitly indicate whether a given range is an equality range.  This makes a fix to bug#38793 trickier, as this must be determined from the data passed.  It is suggested that an equality bound can be directly specified.
  - The API should remain backwards compatible where possible,
[28 Jan 2009 12:28] Frazer Clement
Notes on 'distribution keys' : 
For user-defined partitioning, DIH treats the passed 'distribution key' as a literal partition id.
For traditional LINEAR_KEY and KEY partitioning, DIH treats the passed 'distribution key' as a hash result and performs linear or modulo division to get the partition id.
For HASHMAP partitioning, DIH treats the passed 'distribution key' as a hashvalue and uses modulo division to get a hashmap index which is used to lookup the hashmap and get the actual partitionid.

Ndb::computeHash() can be used to calculate a hash value from a distribution key.
[17 Feb 2009 19:13] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/66700

2857 Frazer Clement	2009-02-17
      Bug#37934 Partition pruning doesn't work for all queries in DBT2
      
      New proposed fix against mysql-5.1-telco-6.3 which allows 
      partition information to be supplied for each range in a multi
      range index scan.
      NdbApi changes : 
        - Partition information is supplied using the new Ndb::PartitionSpec
      type which can be supplied distribution key values for natively
      partitioned tables, or an explicit partitionId for user-defined
      partitioned tables.
          NdbApi IndexScan code takes care of ensuring that all partitions 
      specified against a range being scanned will be scanned.  In the
      current implementation, this means that if the ranges require >1
      partition to be scanned then all partitions are scanned.
      
        - The ScanOptions structure is augmented with a new option - 
      SO_PART_INFO which allows partitioning information for a whole
      scan (table or index) to be defined in terms of an Ndb::PartitionSpec.
      
        - NdbIndexScanOperation::end_of_bound() is extended to accept per-range
      partitioning information
      
      MySQLD changes
        - The new per-range partitioning information Api is used when defining
          a MRR index scan
      
      Testing
        - New testcase ndb_partitioning_hash is added with Mikael's suggested
          testcase
        - storage/ndb/test/ndbapi/testPartitioning is extended to test the
          new per-range partitioning info Api, with user-defined partitioned
          tables
      added:
        mysql-test/suite/ndb/r/ndb_partition_hash.result
        mysql-test/suite/ndb/t/ndb_partition_hash.test
      modified:
        sql/ha_ndbcluster.cc
        storage/ndb/include/ndbapi/Ndb.hpp
        storage/ndb/include/ndbapi/NdbIndexScanOperation.hpp
        storage/ndb/include/ndbapi/NdbOperation.hpp
        storage/ndb/include/ndbapi/NdbScanOperation.hpp
        storage/ndb/src/ndbapi/Ndb.cpp
        storage/ndb/src/ndbapi/NdbOperationSearch.cpp
        storage/ndb/src/ndbapi/NdbScanOperation.cpp
        storage/ndb/src/ndbapi/ndberror.c
        storage/ndb/test/include/HugoOperations.hpp
        storage/ndb/test/ndbapi/testPartitioning.cpp
        storage/ndb/test/src/HugoOperations.cpp
        storage/ndb/test/src/HugoTransactions.cpp

=== added file 'mysql-test/suite/ndb/r/ndb_partition_hash.result'
--- a/mysql-test/suite/ndb/r/ndb_partition_hash.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/ndb/r/ndb_partition_hash.result	2009-02-17 19:12:06 +0000
@@ -0,0 +1,23 @@
+drop table if exists t1;
+CREATE TABLE t1 (
+ol_o_id int NOT NULL,
+ol_d_id int NOT NULL,
+ol_w_id int NOT NULL,
+ol_number int NOT NULL,
+ol_tmp int,
+PRIMARY KEY (ol_w_id, ol_d_id, ol_o_id, ol_number)
+) ENGINE=NDB
+PARTITION BY HASH (ol_w_id);
+insert into t1 values (1,0,0,0,0),(1,0,0,1,0),(1,0,0,2,0),(1,1,0,0,0),(1,1,0,1,0);
+insert into t1 values (2,0,0,0,0),(4,0,0,0,0),(4,0,0,1,0);
+insert into t1 values (0,1,1,4,0),(0,1,1,5,0);
+UPDATE t1 force index (primary) SET ol_tmp = 1
+WHERE ol_o_id = 0 AND ol_d_id = 1 AND ol_w_id = 1;
+SELECT * FROM t1 WHERE ol_w_id = 1 AND ol_d_id = 1 AND ol_o_id = 0;
+ol_o_id	ol_d_id	ol_w_id	ol_number	ol_tmp
+0	1	1	4	1
+0	1	1	5	1
+SELECT SUM(ol_number) FROM t1 WHERE ol_o_id = 0 AND ol_w_id = 1 AND ol_d_id = 1;
+SUM(ol_number)
+9
+drop table t1;

=== added file 'mysql-test/suite/ndb/t/ndb_partition_hash.test'
--- a/mysql-test/suite/ndb/t/ndb_partition_hash.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/ndb/t/ndb_partition_hash.test	2009-02-17 19:12:06 +0000
@@ -0,0 +1,49 @@
+--source include/have_ndb.inc
+#
+# Simple test for the partition storage engine
+# Focuses on range partitioning tests
+# 
+#-- source include/have_partition.inc
+
+--disable_query_log
+set new=on;
+--enable_query_log
+
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+
+#
+# Partition by hash, basic
+#
+
+
+#
+# BUG37934:
+# Partition pruning is broken for DBT2 table
+# Testcase from Mikael Ronstrom.
+# Note that the testcase did not fail previously, but accessed
+# partitions unnecessarily
+#
+CREATE TABLE t1 (
+  ol_o_id int NOT NULL,
+  ol_d_id int NOT NULL,
+  ol_w_id int NOT NULL,
+  ol_number int NOT NULL,
+  ol_tmp int,
+  PRIMARY KEY (ol_w_id, ol_d_id, ol_o_id, ol_number)
+) ENGINE=NDB
+PARTITION BY HASH (ol_w_id);
+
+insert into t1 values (1,0,0,0,0),(1,0,0,1,0),(1,0,0,2,0),(1,1,0,0,0),(1,1,0,1,0);
+insert into t1 values (2,0,0,0,0),(4,0,0,0,0),(4,0,0,1,0);
+insert into t1 values (0,1,1,4,0),(0,1,1,5,0); # Only rows affected.
+
+UPDATE t1 force index (primary) SET ol_tmp = 1
+WHERE ol_o_id = 0 AND ol_d_id = 1 AND ol_w_id = 1;
+
+SELECT * FROM t1 WHERE ol_w_id = 1 AND ol_d_id = 1 AND ol_o_id = 0;
+SELECT SUM(ol_number) FROM t1 WHERE ol_o_id = 0 AND ol_w_id = 1 AND ol_d_id = 1;
+
+drop table t1;
+

=== modified file 'sql/ha_ndbcluster.cc'
--- a/sql/ha_ndbcluster.cc	2009-02-17 09:33:45 +0000
+++ b/sql/ha_ndbcluster.cc	2009-02-17 19:12:06 +0000
@@ -2738,6 +2738,7 @@ ha_ndbcluster::pk_unique_index_read_key(
 
   if (ppartition_id != NULL)
   {
+    assert(m_user_defined_partitioning);
     options.optionsPresent|= NdbOperation::OperationOptions::OO_PARTITION_ID;
     options.partitionId= *ppartition_id;
     poptions= &options;
@@ -2902,6 +2903,13 @@ int ha_ndbcluster::ordered_index_scan(co
   if (m_use_partition_pruning && part_spec != NULL &&
       part_spec->start_part == part_spec->end_part)
   {
+    /* TODO 
+     * Really should only take this path when the table uses
+     * user-defined partitioning, otherwise we need a key value
+     * to correctly constrain the partition scanned.
+     * Could pass this as range-specific partitioning info - might 
+     * be clearer.  Though as this is a single-range scan, no need
+     */
     options.partitionId = part_spec->start_part;
     options.optionsPresent |= NdbScanOperation::ScanOptions::SO_PARTITION_ID;
   }
@@ -2916,6 +2924,8 @@ int ha_ndbcluster::ordered_index_scan(co
                              sizeof(NdbScanOperation::ScanOptions))))
     ERR_RETURN(trans->getNdbError());
 
+  DBUG_PRINT("info", ("Is scan pruned to 1 partition? : %u", op->getPruned()));
+
   if (uses_blob_value(table->read_set) &&
       get_blob_values(op, NULL, table->read_set) != 0)
     ERR_RETURN(op->getNdbError());
@@ -2994,6 +3004,15 @@ int ha_ndbcluster::full_table_scan(const
        * don't need it anymore since output from one ordered partitioned
        * index is always sorted.
        */
+      // TODO : We are still allowing a table scan of a natively partitioned
+      // table to be pruned by MySQLD.  This is probably not a good idea.
+      // It is used as part of ALTER TABLE COALESCE PARTITION in the 
+      // ndb_partition_key test.
+      // Really there should be some way to specify how a table scan of
+      // a natively partitioned table can be pruned (i.e. supply 
+      // distribution key values), though it's not clear how to do this
+      // in cooperation with the MySQLD partitioning.
+      //
       use_set_part_id= TRUE;
       if (!(trans= get_transaction_part_id(part_spec.start_part, error)))
       {
@@ -3021,6 +3040,8 @@ int ha_ndbcluster::full_table_scan(const
   options.parallel = parallelism;
 
   if (use_set_part_id) {
+    // TODO : Note that we can call this for natively partitioned tables
+    // See comment above about resolving this.
     options.optionsPresent|= NdbScanOperation::ScanOptions::SO_PARTITION_ID;
     options.partitionId = part_spec.start_part;
   };
@@ -10691,6 +10712,10 @@ ha_ndbcluster::read_multi_range_first(KE
       {
         if (part_spec.start_part == part_spec.end_part)
         {
+          /* MySQLD thinks this key is definitely in one partition so 
+           * we'll hint the transaction to run there.
+           * No big loss if it's transiently incorrect
+           */
           if (unlikely((trans= start_transaction_part_id(part_spec.start_part, error)) == NULL))
           {
             DBUG_RETURN(error);
@@ -10714,14 +10739,18 @@ ha_ndbcluster::read_multi_range_first(KE
     if (read_multi_needs_scan(cur_index_type, key_info, r))
     {
       any_real_read= TRUE;
+      DBUG_PRINT("info", ("any_real_read= TRUE"));
+      
       /*
         If we reach the limit of ranges allowed in a single scan: stop
         here, send what we have so far, and continue when done with that.
       */
-      DBUG_PRINT("info", ("Reached the limit of ranges allowed in a single"
-                          "scan, any_real_read= TRUE"));
       if (i > NdbIndexScanOperation::MaxRangeNo)
+      {
+        DBUG_PRINT("info", ("Reached the limit of ranges allowed in a single"
+                            "scan"));
         break;
+      }
 
       /* Create the scan operation for the first scan range. */
       if (!m_multi_cursor)
@@ -10777,13 +10806,34 @@ ha_ndbcluster::read_multi_range_first(KE
         m_next_row= 0;
       }
 
+      Ndb::PartitionSpec ndbPartitionSpec;
+      const Ndb::PartitionSpec* ndbPartSpecPtr= NULL;
+
+      /* If this table uses user-defined partitioning, use MySQLD provided
+       * partition info as pruning info
+       * Otherwise, scan range pruning is performed automatically by
+       * NDBAPI based on distribution key values.
+       */
+      if (m_use_partition_pruning && 
+          m_user_defined_partitioning &&
+          (part_spec.start_part == part_spec.end_part))
+      {
+        DBUG_PRINT("info", ("Range on user-def-partitioned table can be pruned to part %u",
+                            part_spec.start_part));
+        ndbPartitionSpec.type= Ndb::PartitionSpec::PS_USER_DEFINED;
+        ndbPartitionSpec.UserDefined.partitionId= part_spec.start_part;
+        ndbPartSpecPtr= &ndbPartitionSpec;
+      }
+
       /* Include this range in the ordered index scan. */
       NdbIndexScanOperation::IndexBound bound;
       compute_index_bounds(bound, key_info, &r->start_key, &r->end_key);
       bound.range_no= i;
 
       if (m_multi_cursor->setBound(m_index[active_index].ndb_record_key,
-                                   bound))
+                                   bound,
+                                   ndbPartSpecPtr, // Only for user-def tables
+                                   sizeof(Ndb::PartitionSpec)))
       {
         ERR_RETURN(trans->getNdbError());
       }
@@ -10842,6 +10892,12 @@ ha_ndbcluster::read_multi_range_first(KE
 
   buffer->end_of_used_area= row_buf;
 
+  if (m_multi_cursor)
+  {
+    DBUG_PRINT("info", ("Is MRR scan pruned to 1 partition? :%u",
+                        m_multi_cursor->getPruned()));
+  };
+
   if (any_real_read)
   {
     /* Get pointer to first range key operation (not scans) */

=== modified file 'storage/ndb/include/ndbapi/Ndb.hpp'
--- a/storage/ndb/include/ndbapi/Ndb.hpp	2008-11-06 16:46:27 +0000
+++ b/storage/ndb/include/ndbapi/Ndb.hpp	2009-02-17 19:12:06 +0000
@@ -1339,7 +1339,9 @@ public:
 
 
   /**
-   * Structure for passing in pointers to startTransaction
+   * Structure for passing in pointers to distribution key values
+   * When distribution key has multiple parts, they should be
+   * passed as an array, with the last part's ptr == NULL.
    * 
    */
   struct Key_part_ptr
@@ -1349,6 +1351,47 @@ public:
   };
 
   /**
+   * Structure for describing a table partition in terms of either
+   * 
+   * PS_NONE
+   *   No partitioning info provided.
+   *
+   * PS_USER_DEFINED
+   *   A specific partition id for a table with user defined 
+   *   partitioning
+   *
+   * PS_DISTR_KEY_PART_PTR
+   *   An array of a table's distribution key values for a 
+   *   table with native partitioning.
+   *
+   */
+
+  struct PartitionSpec
+  {
+    enum SpecType
+    {
+      PS_NONE                = 0,
+      PS_USER_DEFINED        = 1,
+      PS_DISTR_KEY_PART_PTR  = 2
+    };
+
+    Uint32 type;
+    
+    union
+    {
+      struct {
+        Uint32 partitionId;
+      } UserDefined;
+      
+      struct {
+        const Key_part_ptr* tableKeyParts;
+        void* xfrmbuf;
+        Uint32 xfrmbuflen;
+      } KeyPartPtr;
+    };
+  };
+
+  /**
    * Start a transaction
    *
    * @note When the transaction is completed it must be closed using
@@ -1384,7 +1427,7 @@ public:
                                    Uint32 partitionId);
 
   /**
-   * Compute hash value given table/keys
+   * Compute distribution hash value given table/keys
    *
    * @param  hashvalueptr - OUT, is set to hashvalue if return value is 0
    * @param  table    Pointer to table object
@@ -1398,6 +1441,7 @@ public:
    *
    * @note if xfrmbuf is null (default) malloc/free will be made
    *       if xfrmbuf is not null but length is too short, method will fail
+   *       Only for use with natively partitioned tables.
    *
    * @return 0 - ok - hashvalueptr is set
    *         else - fail, return error code

=== modified file 'storage/ndb/include/ndbapi/NdbIndexScanOperation.hpp'
--- a/storage/ndb/include/ndbapi/NdbIndexScanOperation.hpp	2008-08-26 10:07:22 +0000
+++ b/storage/ndb/include/ndbapi/NdbIndexScanOperation.hpp	2009-02-17 19:12:06 +0000
@@ -162,8 +162,12 @@ public:
    * values must be strictly increasing (i.e. starting at zero and 
    * getting larger by 1 for each range specified).  This is to ensure 
    * that rows are returned in order.
+   * If extra partitioning constraint information is known then it
+   * can be supplied.
    */
-  int end_of_bound(Uint32 range_no= 0);
+  int end_of_bound(Uint32 range_no= 0,
+                   const Ndb::PartitionSpec* partInfo= 0, 
+                   Uint32 sizeOfPartInfo = 0);
 
   /**
    * Return range number for current row, as defined in the IndexBound
@@ -197,24 +201,37 @@ public:
   };
 
   /**
-   * Add a bound to an NdbRecord defined Index scan
+   * Add a range to an NdbRecord defined Index scan
    * 
-   * This method is called to add a bound to an IndexScan operation
+   * This method is called to add a range to an IndexScan operation
    * which has been defined with a call to NdbTransaction::scanIndex().
-   * To add extra bounds, the index scan operation must have been
+   * To add more than one range, the index scan operation must have been
    * defined with the the SF_MultiRange flag set.
    *
    * Where multiple numbered ranges are defined with multiple calls to 
-   * setBound, and the scan is ordered, the range number for each bound 
-   * must be larger than the range number for the previously defined bound.
+   * setBound, and the scan is ordered, the range number for each range 
+   * must be larger than the range number for the previously defined range.
    * 
-   * @param key_record NdbRecord structure for the key the bound is 
-   *        to be added to
+   * When the application knows that rows in-range will only be found in
+   * a particular partition, a PartitionSpecification can be supplied.
+   * This may be used to limit the scan to a single partition, improving
+   * system efficiency
+   * The sizeOfPartInfo parameter should be set to the 
+   * sizeof(PartitionSpec) to enable backwards compatibility.
+   * 
+   * @param key_record NdbRecord structure for the key the index is 
+   *        defined on
    * @param bound The bound to add
+   * @param partInfo Extra information to enable a reduced set of
+   *        partitions to be scanned.
+   * @param sizeOfPartInfo
+   *
    * @return 0 for Success, other for Failure.
    */
-  int setBound(const NdbRecord *key_record,
-               const IndexBound& bound);
+  int setBound(const NdbRecord* key_record,
+               const IndexBound& bound,
+               const Ndb::PartitionSpec* partInfo= 0,
+               Uint32 sizeOfPartInfo= 0);
 
   /**
    * Is current scan sorted?
@@ -270,6 +287,9 @@ private:
 
       IndexBound ib;
     };
+
+    Ndb::PartitionSpec pSpec;
+
     /* Space for key bounds 
      *   Low bound from offset 0
      *   High bound from offset key_record->m_row_size
@@ -288,8 +308,9 @@ private:
                            const void *aValue);
 
   int setBound(const NdbColumnImpl*, int type, const void* aValue);
-  int buildIndexBoundOldApi(int range_no);
+  int buildIndexBoundOldApi(int range_no, const Ndb::PartitionSpec* partInfo);
   const IndexBound* getIndexBoundFromRecAttr(NdbRecAttr* recAttr);
+  const Ndb::PartitionSpec* getPartInfoFromRecAttr(NdbRecAttr* recAttr);
   void releaseIndexBoundsOldApi();
   int insertBOUNDS(Uint32 * data, Uint32 sz);
   int ndbrecord_insert_bound(const NdbRecord *key_record,
@@ -300,10 +321,10 @@ private:
   virtual int equal_impl(const NdbColumnImpl*, const char*);
   virtual NdbRecAttr* getValue_impl(const NdbColumnImpl*, char*);
 
-  void setDistKeyFromRange(const NdbRecord *key_record,
-                           const NdbRecord *result_record,
-                           const char *row,
-                           Uint32 distkeyMax);
+  int getDistKeyFromRange(const NdbRecord* key_record,
+                          const NdbRecord* result_record,
+                          const char* row,
+                          Uint32* distKey);
   void fix_get_values();
   int next_result_ordered(bool fetchAllowed, bool forceSend = false);
   int next_result_ordered_ndbrecord(const char * & out_row,

=== modified file 'storage/ndb/include/ndbapi/NdbOperation.hpp'
--- a/storage/ndb/include/ndbapi/NdbOperation.hpp	2008-12-09 15:47:14 +0000
+++ b/storage/ndb/include/ndbapi/NdbOperation.hpp	2009-02-17 19:12:06 +0000
@@ -1063,8 +1063,6 @@ public:
 
 
 protected:
-  int handle_distribution_key(const NdbColumnImpl*, const Uint64 *, Uint32 len);
-protected:
 /******************************************************************************
  * These are the methods used to create and delete the NdbOperation objects.
  *****************************************************************************/

=== modified file 'storage/ndb/include/ndbapi/NdbScanOperation.hpp'
--- a/storage/ndb/include/ndbapi/NdbScanOperation.hpp	2009-02-10 09:06:59 +0000
+++ b/storage/ndb/include/ndbapi/NdbScanOperation.hpp	2009-02-17 19:12:06 +0000
@@ -114,7 +114,9 @@ public:
                 SO_GETVALUE     = 0x08,
                 SO_PARTITION_ID = 0x10,
                 SO_INTERPRETED  = 0x20,
-                SO_CUSTOMDATA   = 0x40 };
+                SO_CUSTOMDATA   = 0x40,
+                SO_PART_INFO    = 0x80
+    };
 
     /* Flags controlling scan behaviour
      * See NdbScanOperation::ScanFlag for details
@@ -138,7 +140,11 @@ public:
     NdbOperation::GetValueSpec *extraGetValues;
     Uint32                     numExtraGetValues;
 
-    /* Specific partition to limit this scan to */
+    /* Specific partition to limit this scan to
+     * Alternatively, an Ndb::PartitionSpec can be supplied.
+     * For Index Scans, partitioning information can be supplied for
+     * each range
+     */
     Uint32 partitionId;
 
     /* Interpreted code to execute as part of the scan */
@@ -146,6 +152,10 @@ public:
 
     /* CustomData ptr to associate with the scan operation */
     void * customData;
+
+    /* Partition information for bounding this scan */
+    const Ndb::PartitionSpec* partitionInfo;
+    Uint32 sizeOfPartInfo;
   };
 
 
@@ -185,6 +195,58 @@ public:
   inline int readTuplesExclusive(int parallell = 0){
     return readTuples(LM_Exclusive, 0, parallell);
   }
+
+  /* First version of ScanOptions, defined here for backwards
+   * compatibility reasons
+   */
+  struct ScanOptions_v1
+  {
+    /* Which options are present - see below for possibilities */
+    Uint64 optionsPresent;
+
+    enum Type { SO_SCANFLAGS    = 0x01,
+                SO_PARALLEL     = 0x02,
+                SO_BATCH        = 0x04,
+                SO_GETVALUE     = 0x08,
+                SO_PARTITION_ID = 0x10,
+                SO_INTERPRETED  = 0x20,
+                SO_CUSTOMDATA   = 0x40 };
+
+    /* Flags controlling scan behaviour
+     * See NdbScanOperation::ScanFlag for details
+     */
+    Uint32 scan_flags;
+
+    /* Desired scan parallelism.
+     * Default == 0 == Maximum parallelism
+     */
+    Uint32 parallel;
+
+    /* Desired scan batchsize in rows 
+     * for NDBD -> API transfers
+     * Default == 0 == Automatically chosen size
+     */
+    Uint32 batch;
+    
+    /* Extra values to be read for each row meeting
+     * scan criteria
+     */
+    NdbOperation::GetValueSpec *extraGetValues;
+    Uint32                     numExtraGetValues;
+
+    /* Specific partition to limit this scan to
+     * Only applicable for tables defined with UserDefined partitioning
+     * For Index Scans, partitioning information can be supplied for
+     * each range
+     */
+    Uint32 partitionId;
+
+    /* Interpreted code to execute as part of the scan */
+    const NdbInterpretedCode *interpretedCode;
+
+    /* CustomData ptr to associate with the scan operation */
+    void * customData;
+  };
 #endif
   
 #ifndef DOXYGEN_SHOULD_SKIP_INTERNAL
@@ -388,6 +450,14 @@ public:
    */
   NdbTransaction* getNdbTransaction() const;
 
+
+  /**
+   * Is scan operation pruned to a single table partition?
+   * For NdbRecord defined scans, valid before+after execute.
+   * For Old Api defined scans, valid only after execute.
+   */
+  bool getPruned() const;
+
 protected:
   NdbScanOperation(Ndb* aNdb,
                    NdbOperation::Type aType = NdbOperation::TableScan);
@@ -400,7 +470,15 @@ protected:
   int handleScanGetValuesOldApi();
   int addInterpretedCode(Uint32 aTC_ConncetPtr,
                          Uint64 aTransId);
+  int handleScanOptionsVersion(const ScanOptions*& optionsPtr, 
+                               Uint32 sizeOfOptions,
+                               ScanOptions& currOptions);
   int handleScanOptions(const ScanOptions *options);
+  int validatePartInfoPtr(const Ndb::PartitionSpec*& partInfo,
+                          Uint32 sizeOfPartInfo);
+  int getPartValueFromInfo(const Ndb::PartitionSpec* partInfo,
+                           const NdbTableImpl* table,
+                           Uint32* partValue);
   int generatePackedReadAIs(const NdbRecord *reseult_record, bool& haveBlob);
   int scanImpl(const NdbScanOperation::ScanOptions *options);
   int scanTableImpl(const NdbRecord *result_record,
@@ -565,6 +643,16 @@ protected:
    * old NdbScanFilter Api
    */
   NdbInterpretedCode* m_interpretedCodeOldApi;
+
+  enum ScanPruningState {
+    SPS_UNKNOWN,           // Initial state
+    SPS_FIXED,             // Explicit partitionId passed in ScanOptions
+    SPS_ONE_PARTITION,     // Scan pruned to one partition by previous range
+    SPS_MULTI_PARTITION    // Scan cannot be pruned due to previous ranges
+  };
+  
+  ScanPruningState m_pruneState;
+  Uint32 m_pruningKey;  // Can be distr key hash or actual partition id.
 };
 
 inline
@@ -674,4 +762,15 @@ NdbScanOperation::getNdbTransaction() co
   return m_transConnection;
 };
 
+inline
+bool
+NdbScanOperation::getPruned() const
+{
+  /* Note that for old Api scans, the bounds are not added until 
+   * execute() time, so this will return false until after execute
+   */
+  return ((m_pruneState == SPS_ONE_PARTITION) ||
+          (m_pruneState == SPS_FIXED));
+}
+
 #endif

=== modified file 'storage/ndb/src/ndbapi/Ndb.cpp'
--- a/storage/ndb/src/ndbapi/Ndb.cpp	2009-02-10 16:30:54 +0000
+++ b/storage/ndb/src/ndbapi/Ndb.cpp	2009-02-17 19:12:06 +0000
@@ -300,11 +300,12 @@ Ndb::waitUntilReady(int timeout)
 }
 
 /*****************************************************************************
-NdbTransaction* startTransaction();
+NdbTransaction* computeHash()
 
-Return Value:   Returns a pointer to a connection object.
-                Return NULL otherwise.
-Remark:         Start transaction. Synchronous.
+Return Value:   Returns 0 for success, NDBAPI error code otherwise
+Remark:         Computes the distribution hash value for a row with the
+                supplied distribtion key values.
+                Only relevant for natively partitioned tables.
 *****************************************************************************/ 
 int
 Ndb::computeHash(Uint32 *retval,
@@ -321,6 +322,15 @@ Ndb::computeHash(Uint32 *retval,
 
   Uint32 colcnt = impl->m_columns.size();
   Uint32 parts = impl->m_noOfDistributionKeys;
+
+  if (unlikely(impl->m_fragmentType == NdbDictionary::Object::UserDefined))
+  {
+    /* Calculating native hash on keys in user defined 
+     * partitioned table is probably part of a bug
+     */
+    goto euserdeftable;
+  }
+
   if (parts == 0)
   {
     parts = impl->m_noOfKeys;
@@ -455,6 +465,9 @@ Ndb::computeHash(Uint32 *retval,
   
   return 0;
   
+euserdeftable:
+  return 4544;
+  
 enullptr:
   return 4316;
   

=== modified file 'storage/ndb/src/ndbapi/NdbOperationSearch.cpp'
--- a/storage/ndb/src/ndbapi/NdbOperationSearch.cpp	2008-11-08 21:06:51 +0000
+++ b/storage/ndb/src/ndbapi/NdbOperationSearch.cpp	2009-02-17 19:12:06 +0000
@@ -522,49 +522,6 @@ NdbOperation::getKeyFromTCREQ(Uint32* da
   return 0;
 }
 
-int
-NdbOperation::handle_distribution_key(const NdbColumnImpl* tAttrInfo,
-                                      const Uint64* value, Uint32 len)
-{
-  DBUG_ENTER("NdbOperation::handle_distribution_key");
-
-  if (theDistrKeyIndicator_ == 1)
-    DBUG_RETURN(0);
-
-  if (theNoOfTupKeyLeft > 0 || m_accessTable->m_noOfDistributionKeys > 1)
-    DBUG_RETURN(0);
-  
-  DBUG_DUMP("value", (const uchar*)value, len << 2);
-
-  if(m_accessTable->m_noOfDistributionKeys == 1)
-  {
-    Ndb::Key_part_ptr ptrs[2];
-    ptrs[0].ptr = value;
-    ptrs[0].len = len;
-    ptrs[1].ptr = 0;
-    
-    const Uint32 MaxKeyLenInLongWords= (NDB_MAX_KEY_SIZE + 7)/ 8; 
-    Uint64 tmp[ MaxKeyLenInLongWords ]; 
-    Uint32 hashValue;
-    int ret = Ndb::computeHash(&hashValue, 
-                               m_currentTable,
-                               ptrs, tmp, sizeof(tmp));
-    
-    if (ret == 0)
-    {
-      setPartitionId(m_currentTable->getPartitionId(hashValue));
-    }
-#ifdef VM_TRACE
-    else
-    {
-      ndbout << "Err: " << ret << endl;
-      assert(false);
-    }
-#endif
-  }
-  DBUG_RETURN(0);
-}
-
 void
 NdbOperation::setPartitionId(Uint32 value)
 {
@@ -576,6 +533,11 @@ NdbOperation::setPartitionId(Uint32 valu
     return; // TODO : Consider adding int rc for error
   }
 
+  /* Todo
+   * We really should only allow this variant for user-defined
+   * partitioned tables
+   */
+
   theDistributionKey = value;
   theDistrKeyIndicator_ = 1;
   DBUG_PRINT("info", ("NdbOperation::setPartitionId: %u",

=== modified file 'storage/ndb/src/ndbapi/NdbScanOperation.cpp'
--- a/storage/ndb/src/ndbapi/NdbScanOperation.cpp	2009-02-10 09:06:59 +0000
+++ b/storage/ndb/src/ndbapi/NdbScanOperation.cpp	2009-02-17 19:12:06 +0000
@@ -123,6 +123,7 @@ NdbScanOperation::init(const NdbTableImp
   m_scanUsingOldApi= true;
   m_readTuplesCalled= false;
   m_interpretedCodeOldApi= NULL;
+  m_pruneState= SPS_UNKNOWN;
 
   m_api_receivers_count = 0;
   m_current_api_receiver = 0;
@@ -288,6 +289,19 @@ NdbScanOperation::handleScanOptions(cons
   {
     /* Should not have any blobs defined at this stage */
     assert(theBlobList == NULL);
+    assert(m_pruneState == SPS_UNKNOWN);
+    
+    // TODO : Ideally we could insist that an explicit partition id
+    //        is only allowed for user-defined partitioned tables.
+    //        However, alter table for partitions currently uses this
+    //        (ndb_partition_key test)
+    //
+    //assert(m_attribute_record->table->m_fragmentType == 
+    //  NdbDictionary::Object::UserDefined);
+    m_pruneState= SPS_FIXED;
+    m_pruningKey= options->partitionId;
+    
+    /* And set the vars in the operation now too */
     theDistributionKey = options->partitionId;
     theDistrKeyIndicator_ = 1;
     DBUG_PRINT("info", ("NdbScanOperation::handleScanOptions(dist key): %u",
@@ -327,6 +341,28 @@ NdbScanOperation::handleScanOptions(cons
     m_customData = options->customData;
   }
 
+  /* Preferred form of partitioning information */
+  if (options->optionsPresent & ScanOptions::SO_PART_INFO)
+  {
+    Uint32 partValue;
+    const Ndb::PartitionSpec* pSpec= options->partitionInfo;
+    if (unlikely(validatePartInfoPtr(pSpec,
+                                     options->sizeOfPartInfo) ||
+                 getPartValueFromInfo(pSpec,
+                                      m_currentTable,
+                                      &partValue)))
+      return -1;
+    
+    assert(m_pruneState == SPS_UNKNOWN);
+    m_pruneState= SPS_FIXED;
+    m_pruningKey= partValue;
+    
+    theDistributionKey= partValue;
+    theDistrKeyIndicator_= 1;
+    DBUG_PRINT("info", ("Set distribution key from partition spec to %u",
+                        partValue));
+  }
+
   return 0;
 }
 
@@ -473,6 +509,50 @@ NdbScanOperation::scanImpl(const NdbScan
   return 0;
 }
 
+int
+NdbScanOperation::handleScanOptionsVersion(const ScanOptions*& optionsPtr, 
+                                           Uint32 sizeOfOptions,
+                                           ScanOptions& currOptions)
+{
+  /* Handle different sized ScanOptions */
+  if (unlikely((sizeOfOptions !=0) &&
+               (sizeOfOptions != sizeof(ScanOptions))))
+  {
+    /* Different size passed, perhaps it's an old client */
+    if (sizeOfOptions == sizeof(ScanOptions_v1))
+    {
+      const ScanOptions_v1* oldOptions= 
+        (const ScanOptions_v1*) optionsPtr;
+
+      /* v1 of ScanOptions, copy into current version
+       * structure and update options ptr
+       */
+      currOptions.optionsPresent= oldOptions->optionsPresent;
+      currOptions.scan_flags= oldOptions->scan_flags;
+      currOptions.parallel= oldOptions->parallel;
+      currOptions.batch= oldOptions->batch;
+      currOptions.extraGetValues= oldOptions->extraGetValues;
+      currOptions.numExtraGetValues= oldOptions->numExtraGetValues;
+      currOptions.partitionId= oldOptions->partitionId;
+      currOptions.interpretedCode= oldOptions->interpretedCode;
+      currOptions.customData= oldOptions->customData;
+      
+      /* New fields */
+      currOptions.partitionInfo= NULL;
+      currOptions.sizeOfPartInfo= 0;
+      
+      optionsPtr= &currOptions;
+    }
+    else
+    {
+      /* No other versions supported currently */
+      setErrorCodeAbort(4298);
+      /* Invalid or unsupported ScanOptions structure */
+      return -1;
+    }
+  }
+  return 0;
+}
 
 int
 NdbScanOperation::scanTableImpl(const NdbRecord *result_record,
@@ -486,22 +566,13 @@ NdbScanOperation::scanTableImpl(const Nd
   Uint32 parallel = 0;
   Uint32 batch = 0;
 
+  ScanOptions currentOptions;
+
   if (options != NULL)
   {
-    /* Check options size for versioning... */
-    if (unlikely((sizeOfOptions !=0) &&
-                 (sizeOfOptions != sizeof(ScanOptions))))
-    {
-      /* Handle different sized ScanOptions
-       * Probably smaller is old version, larger is new version
-       */
-      
-      /* No other versions supported currently */
-      setErrorCodeAbort(4298);
-      /* Invalid or unsupported ScanOptions structure */
+    if (handleScanOptionsVersion(options, sizeOfOptions, currentOptions))
       return -1;
-    }
-    
+
     /* Process some initial ScanOptions - most are 
      * handled later
      */
@@ -534,6 +605,42 @@ NdbScanOperation::scanTableImpl(const Nd
 }
 
 
+int
+NdbScanOperation::getPartValueFromInfo(const Ndb::PartitionSpec* partInfo,
+                                       const NdbTableImpl* table,
+                                       Uint32* partValue)
+{
+  if (partInfo->type == Ndb::PartitionSpec::PS_USER_DEFINED)
+  {
+    assert(table->m_fragmentType == NdbDictionary::Object::UserDefined);
+    *partValue= partInfo->UserDefined.partitionId;
+    return 0;
+  }
+  if (partInfo->type == Ndb::PartitionSpec::PS_DISTR_KEY_PART_PTR)
+  {
+    assert(table->m_fragmentType != NdbDictionary::Object::UserDefined);
+    Uint32 hashVal;
+    int ret= Ndb::computeHash(&hashVal, table, 
+                              partInfo->KeyPartPtr.tableKeyParts,
+                              partInfo->KeyPartPtr.xfrmbuf, 
+                              partInfo->KeyPartPtr.xfrmbuflen);
+    if (ret == 0)
+    {
+      *partValue= table->getPartitionId(hashVal);
+      return 0;
+    }
+    else
+    {
+      setErrorCodeAbort(ret);
+      return -1;
+    }
+  }
+  
+  /* 4542 : Unknown partition information type */
+  setErrorCodeAbort(4542);
+  return -1;
+}
+
 /*
   Compare two rows on some prefix of the index.
   This is used to see if we can determine that all rows in an index range scan
@@ -548,6 +655,9 @@ compare_index_row_prefix(const NdbRecord
 {
   Uint32 i;
 
+  if (row1 == row2) // Easy case with same ptrs
+    return 0;
+
   for (i= 0; i<prefix_length; i++)
   {
     const NdbRecord::Attr *col= &rec->columns[rec->key_indexes[i]];
@@ -583,17 +693,21 @@ compare_index_row_prefix(const NdbRecord
   return 0;
 }
 
-void
-NdbIndexScanOperation::setDistKeyFromRange(const NdbRecord *key_record,
+int
+NdbIndexScanOperation::getDistKeyFromRange(const NdbRecord *key_record,
                                            const NdbRecord *result_record,
                                            const char *row,
-                                           Uint32 distkeyMax)
+                                           Uint32* distKey)
 {
   const Uint32 MaxKeySizeInLongWords= (NDB_MAX_KEY_SIZE + 7) / 8; 
   Uint64 tmp[ MaxKeySizeInLongWords ];
   char* tmpshrink = (char*)tmp;
   size_t tmplen = sizeof(tmp);
   
+  /* This can't work for User Defined partitioning */
+  assert(key_record->table->m_fragmentType != 
+         NdbDictionary::Object::UserDefined);
+
   Ndb::Key_part_ptr ptrs[NDB_MAX_NO_OF_ATTRIBUTES_IN_KEY+1];
   Uint32 i;
   for (i = 0; i<key_record->distkey_index_length; i++)
@@ -608,8 +722,9 @@ NdbIndexScanOperation::setDistKeyFromRan
         bool len_ok = col->shrink_varchar(row, len, tmpshrink);
         if (!len_ok)
         {
-          assert(false);
-          return;
+          /* 4209 : Length parameter in equal/setValue is incorrect */
+          setErrorCodeAbort(4209);
+          return -1;
         }
         ptrs[i].ptr = tmpshrink;
         tmpshrink += len;
@@ -617,8 +732,9 @@ NdbIndexScanOperation::setDistKeyFromRan
       }
       else
       {
-        // no buffer...
-        return;
+        /* 4207 : Key size is limited to 4092 bytes */
+        setErrorCodeAbort(4207);
+        return -1;
       }
     }
     else
@@ -634,41 +750,74 @@ NdbIndexScanOperation::setDistKeyFromRan
                              ptrs, tmpshrink, tmplen);
   if (ret == 0)
   {
-    theDistributionKey= result_record->table->getPartitionId(hashValue);
-    theDistrKeyIndicator_= 1;
-
-    ScanTabReq *req= CAST_PTR(ScanTabReq, theSCAN_TABREQ->getDataPtrSend());
-    ScanTabReq::setDistributionKeyFlag(req->requestInfo, 1);
-    req->distributionKey= theDistributionKey;
-    theSCAN_TABREQ->setLength(ScanTabReq::StaticLength + 1);
+    /* Return actual partitionId determined by hash */
+    *distKey= result_record->table->getPartitionId(hashValue);
+    return 0;
   }
-#ifdef VM_TRACE
   else
   {
+#ifdef VM_TRACE
     ndbout << "err: " << ret << endl;
-    assert(false);
-  }
 #endif
+    setErrorCodeAbort(ret);
+    return -1;
+  }
 }
 
+int
+NdbScanOperation::validatePartInfoPtr(const Ndb::PartitionSpec*& partInfo,
+                                      Uint32 sizeOfPartInfo)
+{  
+  if (unlikely((sizeOfPartInfo != sizeof(Ndb::PartitionSpec)) &&
+               (sizeOfPartInfo != 0)))
+  {
+    /* 4545 : Invalid or Unsupported PartitionInfo structure */
+    setErrorCodeAbort(4545);
+    return -1;
+  }
+  
+  if (partInfo->type != Ndb::PartitionSpec::PS_NONE)
+  {
+    if (m_pruneState == SPS_FIXED)
+    {
+      /* 4543 : Duplicate partitioning information supplied */
+      setErrorCodeAbort(4543);
+      return -1;
+    }
+    
+    if ((partInfo->type == Ndb::PartitionSpec::PS_USER_DEFINED) !=
+        ((m_currentTable->m_fragmentType == NdbDictionary::Object::UserDefined)))
+    {
+      /* Mismatch between type of partitioning info supplied, and table's
+       * partitioning type
+       */
+      /* 4544 : Wrong partitionInfo type for table */
+      setErrorCodeAbort(4544);
+      return -1;
+    }
+  }
+  else
+  {
+    /* PartInfo supplied, but set to NONE */
+    partInfo= NULL;
+  }
+
+  return 0;
+}
 
 /** 
  * setBound()
  *
  * This method is called from scanIndex() and setBound().  
  * It adds a bound to an Index Scan.
+ * It can be passed extra partitioning information.
  */
 int 
 NdbIndexScanOperation::setBound(const NdbRecord *key_record,
-                                const IndexBound& bound)
+                                const IndexBound& bound,
+                                const Ndb::PartitionSpec* partInfo,
+                                Uint32 sizeOfPartInfo)
 {
-  /*
-    Set up index range bounds, write into keyinfo.
-    
-    ToDo: We only set scan distribution key if there's only one
-    scan bound. (see BUG#25821).  MRR/BKA does not use it.
-  */
-
   if (unlikely((theStatus != NdbOperation::UseNdbRecord)))
   {
     setErrorCodeAbort(4284);
@@ -691,6 +840,21 @@ NdbIndexScanOperation::setBound(const Nd
     return -1;
   }
 
+  /* Check the base table's partitioning scheme 
+   * (Ordered index itself has 'undefined' fragmentation)
+   */
+  bool tabHasUserDefPartitioning= (m_currentTable->m_fragmentType == 
+                                   NdbDictionary::Object::UserDefined);
+
+  /* Validate explicit partitioning info if it's supplied */
+  if (partInfo)
+  {
+    /* May update the PartInfo ptr */
+    if (validatePartInfoPtr(partInfo,
+                            sizeOfPartInfo))
+      return -1;
+  }
+
   m_num_bounds++;
 
   if (unlikely((m_num_bounds > 1) &&
@@ -777,34 +941,125 @@ NdbIndexScanOperation::setBound(const Nd
   m_first_bound_word= theKEYINFOptr + theTotalNrOfKeyWordInSignal;
   m_this_bound_start= theTupKeyLen;
 
-  /*
-    Now check if the range bounds a single distribution key. If so, we need
-    scan only a single fragment.
-    
-    ToDo: we do not attempt to identify the case where we have multiple
-    ranges, but they all bound the same single distribution key. It seems
-    not really worth the effort to optimise this case, better to fix the
-    multi-range protocol so that the distribution key could be specified
-    individually for each of the multiple ranges.
-  */
-  if (m_num_bounds == 1 &&  
-      ! theDistrKeyIndicator_ && // Partitioning not already specified.
-      ! m_multi_range)           // Only single range optimisation currently
-  {
-    Uint32 index_distkeys = key_record->m_no_of_distribution_keys;
-    Uint32 table_distkeys = m_attribute_record->m_no_of_distribution_keys;
-    Uint32 distkey_min= key_record->m_min_distkey_prefix_length;
-    if (index_distkeys == table_distkeys &&
-        common_key_count >= distkey_min &&
-        bound.low_key &&
-        bound.high_key &&
-        0==compare_index_row_prefix(key_record,
-                                    bound.low_key,
-                                    bound.high_key,
-                                    distkey_min))
-      setDistKeyFromRange(key_record, m_attribute_record,
-                          bound.low_key, distkey_min);
-  }
+
+  /* Now determine if the scan can (continue to) be pruned to one
+   * partition
+   * 
+   * This can only be the case if 
+   *   - There's no overriding partition id/info specified in 
+   *     ScanOptions 
+   *     AND
+   *   - This range scan can be pruned to 1 partition 'value'
+   *     AND
+   *   - All previous ranges (MRR) were partition pruned 
+   *     to the same partition 'value'
+   *
+   * Where partition 'value' is either a partition id or a hash
+   * that maps to one in the kernel.
+   */
+  if ((m_pruneState == SPS_UNKNOWN) ||      // First range
+      (m_pruneState == SPS_ONE_PARTITION))  // Previous ranges are commonly pruned
+  {
+    bool currRangeHasOnePartVal= false;
+    Uint32 currRangePartValue= 0;
+
+    /* Determine whether this range scan can be pruned */
+    if (partInfo)
+    {
+      /* Explicit partitioning info supplied, use it to get a value */
+      currRangeHasOnePartVal= true;
+
+      if (getPartValueFromInfo(partInfo,
+                               m_attribute_record->table,
+                               &currRangePartValue))
+      {
+        return -1;
+      }
+    }
+    else
+    {
+      if (likely(!tabHasUserDefPartitioning))
+      {
+        /* Attempt to get implicit partitioning info from range bounds - 
+         * only possible if they are present and bound a single value 
+         * of the table's distribution keys
+         */
+        Uint32 index_distkeys = key_record->m_no_of_distribution_keys;
+        Uint32 table_distkeys = m_attribute_record->m_no_of_distribution_keys;
+        Uint32 distkey_min= key_record->m_min_distkey_prefix_length;
+        if (index_distkeys == table_distkeys &&   // Index has all base table d-keys
+            common_key_count >= distkey_min &&    // Bounds have all d-keys
+            bound.low_key &&                      // Have both bounds
+            bound.high_key &&
+            0==compare_index_row_prefix(key_record,     // Both bounds are same
+                                        bound.low_key,
+                                        bound.high_key,
+                                        distkey_min))
+        {
+          currRangeHasOnePartVal= true;
+          if (getDistKeyFromRange(key_record, m_attribute_record,
+                                  bound.low_key,
+                                  &currRangePartValue))
+            return -1;
+        }
+      }
+    }
+     
+
+    /* Determine whether this pruned range fits with any existing
+     * range pruning
+     * As we can currently only prune a single scan to one partition
+     * (Not a set of partitions, or a set of partitions per range)
+     * we can only prune if all ranges happen to be prune-able to the
+     * same partition.
+     * In future perhaps Ndb can be enhanced to support partition sets
+     * and/or per-range partition pruning.
+     */
+    const ScanPruningState prevPruneState= m_pruneState;
+    if (currRangeHasOnePartVal)
+    {
+      if (m_pruneState == SPS_UNKNOWN)
+      {
+        /* Prune the scan to use this range's partition value */
+        m_pruneState= SPS_ONE_PARTITION;
+        m_pruningKey= currRangePartValue;
+      }
+      else
+      {
+        /* If this range's partition value is the same as the previous
+         * ranges then we can stay pruned, otherwise we cannot
+         */
+        assert(m_pruneState == SPS_ONE_PARTITION);
+        if (currRangePartValue != m_pruningKey)
+        {
+          /* This range is found in a different partition to previous
+           * range(s).  We cannot prune this scan.
+           */
+          m_pruneState= SPS_MULTI_PARTITION;
+        }
+      }
+    }
+    else
+    {
+      /* This range cannot be scanned by scanning a single partition
+       * Therefore the scan must scan all partitions
+       */
+      m_pruneState= SPS_MULTI_PARTITION;
+    }
+
+    /* Now modify the SCANTABREQ */
+    if (m_pruneState != prevPruneState)
+    {
+      theDistrKeyIndicator_= (m_pruneState == SPS_ONE_PARTITION);
+      theDistributionKey= m_pruningKey;
+
+      ScanTabReq *req= CAST_PTR(ScanTabReq, theSCAN_TABREQ->getDataPtrSend());
+      ScanTabReq::setDistributionKeyFlag(req->requestInfo, theDistrKeyIndicator_);
+      req->distributionKey= theDistributionKey;
+      theSCAN_TABREQ->setLength(ScanTabReq::StaticLength + theDistrKeyIndicator_);
+    }
+  } // if (m_pruneState == UNKNOWN / SPS_ONE_PARTITION)
+
   return 0;
 } // ::setBound();
 
@@ -824,21 +1079,12 @@ NdbIndexScanOperation::scanIndexImpl(con
   Uint32 parallel = 0;
   Uint32 batch = 0;
 
+  ScanOptions currentOptions;
+
   if (options != NULL)
   {
-    /* Check options size for versioning... */
-    if (unlikely((sizeOfOptions !=0) &&
-                 (sizeOfOptions != sizeof(ScanOptions))))
-    {
-      /* Handle different sized ScanOptions
-       * Probably smaller is old version, larger is new version
-       */
-      
-      /* No other versions supported currently */
-      setErrorCodeAbort(4298);
-      /* Invalid or unsupported ScanOptions structure */
+    if (handleScanOptionsVersion(options, sizeOfOptions, currentOptions))
       return -1;
-    }
     
     /* Process some initial ScanOptions here
      * The rest will be handled later
@@ -985,6 +1231,7 @@ NdbScanOperation::processTableScanDefs(N
                                        Uint32 batch)
 {
   m_ordered = m_descending = false;
+  m_pruneState= SPS_UNKNOWN;
   Uint32 fragCount = m_currentTable->m_fragmentCount;
 
   if (parallel > fragCount || parallel == 0) {
@@ -1781,7 +2028,14 @@ int NdbScanOperation::finaliseScanOldApi
   options.parallel= m_savedParallelOldApi;
   options.batch= m_savedBatchOldApi;
 
-  /* customData, interpretedCode or partitionId should 
+  if (theDistrKeyIndicator_ == 1)
+  {
+    /* User has defined a partition id specifically */
+    options.optionsPresent |= ScanOptions::SO_PARTITION_ID;
+    options.partitionId= theDistributionKey;
+  }
+
+  /* customData or interpretedCode should 
    * already be set in the operation members - no need 
    * to pass in as ScanOptions
    */
@@ -1813,7 +2067,7 @@ int NdbScanOperation::finaliseScanOldApi
     if (isop->currentRangeOldApi != NULL)
     {
       /* Add current bound to bound list */
-      if (isop->buildIndexBoundOldApi(0) != 0)
+      if (isop->buildIndexBoundOldApi(0, NULL) != 0)
         return -1;
     }
     
@@ -1840,8 +2094,11 @@ int NdbScanOperation::finaliseScanOldApi
       NdbRecAttr* bound= isop->firstRangeOldApi;
       while (bound != NULL)
       {
+        /* Call NdbRecord setBound API to actually set the bound */
         if (isop->setBound( m_accessTable->m_ndbrecord,
-                            *isop->getIndexBoundFromRecAttr(bound) ) != 0)
+                            *isop->getIndexBoundFromRecAttr(bound),
+                            isop->getPartInfoFromRecAttr(bound),
+                            sizeof(Ndb::PartitionSpec)) != 0)
           return -1;
         
         bound= bound->next();
@@ -2725,6 +2982,8 @@ NdbIndexScanOperation::setBound(const Nd
       boundsDef->oldBound.lowBound.key= &boundsDef->space[ 0 ];
       boundsDef->oldBound.highBound.key= &boundsDef->space[ maxKeyRecordBytes ];
       
+      boundsDef->pSpec.type= Ndb::PartitionSpec::PS_NONE;
+
       currentRangeOldApi= boundSpace;
     }
 
@@ -2784,7 +3043,8 @@ NdbIndexScanOperation::setBound(const Nd
  * -1 == error
  */
 int
-NdbIndexScanOperation::buildIndexBoundOldApi(int range_no)
+NdbIndexScanOperation::buildIndexBoundOldApi(int range_no,
+                                             const Ndb::PartitionSpec* partInfo)
 {
   IndexBound ib;
   OldApiScanRangeDefinition* boundDef=
@@ -2848,6 +3108,15 @@ NdbIndexScanOperation::buildIndexBoundOl
   ib.range_no= range_no;
 
   boundDef->ib= ib;
+  if (partInfo != NULL)
+  {
+    boundDef->pSpec= *partInfo;
+  }
+  else
+  {
+    /* No bound partitioning info */
+    boundDef->pSpec.type= Ndb::PartitionSpec::PS_NONE;
+  }
 
   assert( currentRangeOldApi->next() == NULL );
 
@@ -2877,6 +3146,20 @@ NdbIndexScanOperation::getIndexBoundFrom
   return &((OldApiScanRangeDefinition*)recAttr->aRef())->ib;
 }
 
+const Ndb::PartitionSpec*
+NdbIndexScanOperation::getPartInfoFromRecAttr(NdbRecAttr* recAttr)
+{
+  /* If the partitioning info type is != PS_NONE then 
+   * return a pointer to it
+   */
+  const OldApiScanRangeDefinition* rd= 
+    (const OldApiScanRangeDefinition*) recAttr->aRef();
+  
+  return (rd->pSpec.type == Ndb::PartitionSpec::PS_NONE)?
+    NULL:
+    &rd->pSpec;
+};
+
 
 /* Method called to release any resources allocated by the old 
  * Index Scan bound API
@@ -3545,7 +3828,9 @@ NdbScanOperation::reset_receivers(Uint32
 }
 
 int
-NdbIndexScanOperation::end_of_bound(Uint32 no)
+NdbIndexScanOperation::end_of_bound(Uint32 no,
+                                    const Ndb::PartitionSpec* partInfo,
+                                    Uint32 sizeOfPartInfo)
 {
   DBUG_ENTER("end_of_bound");
   DBUG_PRINT("info", ("Range number %u", no));
@@ -3587,8 +3872,17 @@ NdbIndexScanOperation::end_of_bound(Uint
       return -1;
     }
   }
+
+  /* Check partition info */
+  if (partInfo)
+  {
+    /* May update the partInfo ptr */
+    if (validatePartInfoPtr(partInfo, sizeOfPartInfo))
+      return -1;
+  }
+
   
-  if (buildIndexBoundOldApi(no) != 0)
+  if (buildIndexBoundOldApi(no, partInfo) != 0)
     return -1;
       
   DBUG_RETURN(0);

=== modified file 'storage/ndb/src/ndbapi/ndberror.c'
--- a/storage/ndb/src/ndbapi/ndberror.c	2009-02-10 06:53:05 +0000
+++ b/storage/ndb/src/ndbapi/ndberror.c	2009-02-17 19:12:06 +0000
@@ -589,6 +589,10 @@ ErrorBundle ErrorCodes[] = {
   { 4539, DMEC, AE, "NdbInterpretedCode not supported for operation type" },
   { 4540, DMEC, AE, "Attempt to pass an Index column to createRecord.  Use base table columns only" },
   { 4541, DMEC, AE, "IndexBound has no bound information" },
+  { 4542, DMEC, AE, "Unknown partition information type" },
+  { 4543, DMEC, AE, "Duplicate partitioning information supplied" },
+  { 4544, DMEC, AE, "Wrong partitionInfo type for table" },
+  { 4545, DMEC, AE, "Invalid or Unsupported PartitionInfo structure" },
 
   { 4200, DMEC, AE, "Status Error when defining an operation" },
   { 4201, DMEC, AE, "Variable Arrays not yet supported" },

=== modified file 'storage/ndb/test/include/HugoOperations.hpp'
--- a/storage/ndb/test/include/HugoOperations.hpp	2008-11-17 09:26:25 +0000
+++ b/storage/ndb/test/include/HugoOperations.hpp	2009-02-17 19:12:06 +0000
@@ -53,7 +53,8 @@ public:  
   int pkReadRecord(Ndb*,
                    int record,
                    int numRecords = 1,
-                   NdbOperation::LockMode lm = NdbOperation::LM_Read);
+                   NdbOperation::LockMode lm = NdbOperation::LM_Read,
+                   bool addToScan= false);
   
   int pkReadRandRecord(Ndb*,
                        int records,
@@ -91,6 +92,8 @@ public:  
 		   int rowId);
 
   int equalForRow(NdbOperation*, int rowid);
+
+  bool getPartIdForRow(const NdbOperation* pOp, int rowid, Uint32& partId);
   
   int setValues(NdbOperation*, int rowId, int updateId);
   

=== modified file 'storage/ndb/test/ndbapi/testPartitioning.cpp'
--- a/storage/ndb/test/ndbapi/testPartitioning.cpp	2008-02-19 15:00:29 +0000
+++ b/storage/ndb/test/ndbapi/testPartitioning.cpp	2009-02-17 19:12:06 +0000
@@ -22,6 +22,8 @@
 #define GETNDB(ps) ((NDBT_NdbApiStep*)ps)->getNdb()
 
 static Uint32 max_dks = 0;
+static const Uint32 MAX_FRAGS=48 * 8 * 4; // e.g. 48 nodes, 8 frags/node, 4 replicas
+static Uint16 frag_ng_mappings[MAX_FRAGS];
 
 static
 int
@@ -114,6 +116,41 @@ add_distribution_key(Ndb*, NdbDictionary
   return 0;
 }
 
+
+static
+int
+setUserDefPartitioning(Ndb* ndb, NdbDictionary::Table& tab, int when, void* arg)
+{
+  switch(when){
+  case 0: // Before
+    break;
+  case 1: // After
+    return 0;
+  default:
+    return 0;
+  }
+  
+  /* Following should really be taken from running test system : */
+  const Uint32 numNodes= ndb->get_ndb_cluster_connection().no_db_nodes();
+  const Uint32 numReplicas= 2; // Assumption
+  const Uint32 guessNumNgs= numNodes/2;
+  const Uint32 numNgs= guessNumNgs?guessNumNgs : 1;
+  const Uint32 numFragsPerNode= 2 + (rand() % 3);
+  const Uint32 numPartitions= numReplicas * numNgs * numFragsPerNode;
+
+  tab.setFragmentType(NdbDictionary::Table::UserDefined);
+  tab.setFragmentCount(numPartitions);
+  for (Uint32 i=0; i<numPartitions; i++)
+  {
+    frag_ng_mappings[i]= i % numNgs;
+  }
+  tab.setFragmentData(frag_ng_mappings, sizeof(uint16) * numPartitions);
+
+  ndbout << (NDBT_Table&)tab << endl;
+
+  return 0;
+}
+
 static
 int
 one_distribution_key(Ndb*, NdbDictionary::Table& tab, int when, void* arg)
@@ -169,6 +206,23 @@ run_create_table(NDBT_Context* ctx, NDBT
 }
 
 static int
+run_create_table_udefpart(NDBT_Context* ctx, NDBT_Step* step)
+{
+  if(NDBT_Tables::createTable(GETNDB(step), 
+			      ctx->getTab()->getName(), 
+			      false, false, 
+			      setUserDefPartitioning) == NDBT_OK)
+  {
+    return NDBT_OK;
+  }
+
+  if(GETNDB(step)->getDictionary()->getNdbError().code == 745)
+    return NDBT_OK;
+
+  return NDBT_FAILED;
+}
+
+static int
 run_create_table_smart_scan(NDBT_Context* ctx, NDBT_Step* step)
 {
   if(NDBT_Tables::createTable(GETNDB(step), 
@@ -262,29 +316,29 @@ static int run_create_pk_index_drop(NDBT
 }
 
 static int
-run_tests(Ndb* p_ndb, HugoTransactions& hugoTrans, int records)
+run_tests(Ndb* p_ndb, HugoTransactions& hugoTrans, int records, Uint32 batchSize = 1)
 {
-  if (hugoTrans.loadTable(p_ndb, records) != 0)
+  if (hugoTrans.loadTable(p_ndb, records, batchSize) != 0)
   {
     return NDBT_FAILED;
   }
 
-  if(hugoTrans.pkReadRecords(p_ndb, records) != 0)
+  if(hugoTrans.pkReadRecords(p_ndb, records, batchSize) != 0)
   {
     return NDBT_FAILED;
   }
 
-  if(hugoTrans.pkUpdateRecords(p_ndb, records) != 0)
+  if(hugoTrans.pkUpdateRecords(p_ndb, records, batchSize) != 0)
   {
     return NDBT_FAILED;
   }
 
-  if(hugoTrans.pkDelRecords(p_ndb, records) != 0)
+  if(hugoTrans.pkDelRecords(p_ndb, records, batchSize) != 0)
   {
     return NDBT_FAILED;
   }
 
-  if (hugoTrans.loadTable(p_ndb, records) != 0)
+  if (hugoTrans.loadTable(p_ndb, records, batchSize) != 0)
   {
     return NDBT_FAILED;
   }
@@ -335,8 +389,10 @@ run_pk_dk(NDBT_Context* ctx, NDBT_Step* 
     return NDBT_OK;
 
   HugoTransactions hugoTrans(*tab);
+
+  Uint32 batchSize= ctx->getProperty("BatchSize", (unsigned) 1);
   
-  return run_tests(p_ndb, hugoTrans, records);
+  return run_tests(p_ndb, hugoTrans, records, batchSize);
 }
 
 int
@@ -363,10 +419,11 @@ run_index_dk(NDBT_Context* ctx, NDBT_Ste
     ndbout << "Failed to retreive index: " << name.c_str() << endl;
     return NDBT_FAILED;
   }
+  Uint32 batchSize= ctx->getProperty("BatchSize", (unsigned) 1);
 
   HugoTransactions hugoTrans(*pTab, idx);
   
-  return run_tests(p_ndb, hugoTrans, records);
+  return run_tests(p_ndb, hugoTrans, records, batchSize);
 }
 
 static int
@@ -460,6 +517,8 @@ run_startHint_ordered_index(NDBT_Context
     return NDBT_FAILED;
   }
 
+  bool userDefined = (tab->getFragmentType() == NdbDictionary::Object::UserDefined);
+
   HugoTransactions hugoTrans(*tab, idx);
   if (hugoTrans.loadTable(p_ndb, records) != 0)
   {
@@ -474,30 +533,46 @@ run_startHint_ordered_index(NDBT_Context
   int result = NDBT_OK;
   for(int i = 0; i<records && result == NDBT_OK; i++)
   {
-    char buffer[8000];
-    char* start= buffer + (rand() & 7);
-    char* pos= start;
-    
-    int k = 0;
-    Ndb::Key_part_ptr ptrs[NDB_MAX_NO_OF_ATTRIBUTES_IN_KEY+1];
-    for(int j = 0; j<tab->getNoOfColumns(); j++)
+    NdbTransaction* pTrans= NULL;
+
+    if (!userDefined)
     {
-      if(tab->getColumn(j)->getPartitionKey())
+      char buffer[8000];
+      char* start= buffer + (rand() & 7);
+      char* pos= start;
+      
+      int k = 0;
+      Ndb::Key_part_ptr ptrs[NDB_MAX_NO_OF_ATTRIBUTES_IN_KEY+1];
+      for(int j = 0; j<tab->getNoOfColumns(); j++)
       {
-        //ndbout_c(tab->getColumn(j)->getName());
-        int sz = tab->getColumn(j)->getSizeInBytes();
-        Uint32 real_size;
-        dummy.calcValue(i, j, 0, pos, sz, &real_size);
-        ptrs[k].ptr = pos;
-        ptrs[k++].len = real_size;
-        pos += (real_size + 3) & ~3;
+        if(tab->getColumn(j)->getPartitionKey())
+        {
+          //ndbout_c(tab->getColumn(j)->getName());
+          int sz = tab->getColumn(j)->getSizeInBytes();
+          Uint32 real_size;
+          dummy.calcValue(i, j, 0, pos, sz, &real_size);
+          ptrs[k].ptr = pos;
+          ptrs[k++].len = real_size;
+          pos += (real_size + 3) & ~3;
+        }
       }
+      ptrs[k].ptr = 0;
+      
+      // Now we have the pk, start a hinted transaction
+      pTrans= p_ndb->startTransaction(tab, ptrs);
     }
-    ptrs[k].ptr = 0;
-    
-    // Now we have the pk, start a hinted transaction
-    NdbTransaction* pTrans= p_ndb->startTransaction(tab, ptrs);
-    
+    else
+    {
+      /* User defined partitioning
+       * For Hugo, we can calculate the partition id from 
+       * the record number and the number of partitions
+       */
+      const Uint32 partId= i % tab->getFragmentCount();
+      //ndbout << "Setting Transaction partition Id to " 
+      //       << partId << endl;
+      pTrans= p_ndb->startTransaction(tab, partId);
+    }
+
     // Because we pass an Ordered index here, pkReadRecord will
     // use an index scan on the Ordered index
     HugoOperations ops(*tab, idx);
@@ -531,6 +606,303 @@ run_startHint_ordered_index(NDBT_Context
   return result;
 }
 
+static int
+exec_scan_read(Ndb* p_ndb,
+               HugoOperations* ops, HugoCalculator* calc, 
+               int rows_expected)
+{
+  if (ops->execute_NoCommit(p_ndb) != 0)
+  {
+    g_info << "Execute failed" << endl;
+    return NDBT_FAILED;
+  }
+  
+  if (ops->pIndexScanOp->getPruned() != 1)
+  {
+    g_info << "Error : Scan pruning failed" << endl;
+    return NDBT_FAILED;
+  }
+  
+  int check= 0;
+  int rows_found= 0;
+
+  while ((check = ops->pIndexScanOp->nextResult()) == 0)
+  {
+    rows_found++;
+    if (calc->verifyRowValues(&ops->get_row(0)) != 0){
+      g_info << "Bad row received " << ops->get_row(0) << endl;
+      ops->closeTransaction(p_ndb);
+      return NDBT_FAILED;
+    }
+
+    /* Todo : Could re-hash the data to check it came from
+     * the correct partition, but the error-insert should
+     * take care of this
+     */
+    /* Todo : Could check that we actually got back the data
+     * we asked for, but this is an orthogonal issue
+     */
+  }
+  
+  if (check != 1)
+  {
+    g_info << "Bad rc from nextResult() " << check << endl;
+    return NDBT_FAILED;
+  }
+
+  if (rows_found != rows_expected)
+  {
+    g_info << "Error : Expected " << rows_expected 
+           << " rows, received " << rows_found << endl;
+    return NDBT_FAILED;
+  }
+
+  return NDBT_OK;
+}
+
+
+static int
+run_startHint_ordered_index_mrr(NDBT_Context* ctx, NDBT_Step* step)
+{
+  Ndb* p_ndb = GETNDB(step);
+  int records = ctx->getNumRecords();
+  const NdbDictionary::Table *tab = 
+    p_ndb->getDictionary()->getTable(ctx->getTab()->getName());
+  
+  if(!tab)
+    return NDBT_OK;
+
+  BaseString name;
+  name.assfmt("IND_%s_PK_O", tab->getName());
+  
+  const NdbDictionary::Index * idx = 
+    p_ndb->getDictionary()->getIndex(name.c_str(), tab->getName());
+  
+  if(!idx)
+  {
+    ndbout << "Failed to retreive index: " << name.c_str() << endl;
+    return NDBT_FAILED;
+  }
+
+  bool userDefined = (tab->getFragmentType() == NdbDictionary::Object::UserDefined);
+
+  HugoTransactions hugoTrans(*tab, idx);
+  if (hugoTrans.loadTable(p_ndb, records) != 0)
+  {
+    return NDBT_FAILED;
+  }
+
+  const Uint32 MAX_PARTITIONS= 100;
+  const Uint32 MAX_ROWS_PER_SCAN= 20; // To avoid too much KeyInfo for large Keys.
+  struct PartInfo
+  {
+    HugoOperations* ops;
+    Uint32 opRowCount;
+  };
+
+  PartInfo partInfo[ MAX_PARTITIONS ];
+  for (Uint32 i=0; i< MAX_PARTITIONS; i++)
+  {
+    partInfo[i].ops= NULL;
+    partInfo[i].opRowCount= 0;
+  }
+
+  NdbRestarter restarter;
+  if(restarter.insertErrorInAllNodes(8050) != 0)
+    return NDBT_FAILED;
+  
+  HugoCalculator dummy(*tab);
+  int result = NDBT_OK;
+  for(int i = 0; i<records && result == NDBT_OK; i++)
+  {
+    Uint32 partId= ~0;
+    PartInfo* pInfo= NULL;
+    HugoOperations* ops= NULL;
+
+
+    if (!userDefined)
+    {
+      char buffer[8000];
+      char* start= buffer + (rand() & 7);
+      char* pos= start;
+      
+      int k = 0;
+      Ndb::Key_part_ptr ptrs[NDB_MAX_NO_OF_ATTRIBUTES_IN_KEY+1];
+      for(int j = 0; j<tab->getNoOfColumns(); j++)
+      {
+        if(tab->getColumn(j)->getPartitionKey())
+        {
+          //ndbout_c(tab->getColumn(j)->getName());
+          int sz = tab->getColumn(j)->getSizeInBytes();
+          Uint32 real_size;
+          dummy.calcValue(i, j, 0, pos, sz, &real_size);
+          ptrs[k].ptr = pos;
+          ptrs[k++].len = real_size;
+          pos += (real_size + 3) & ~3;
+        }
+      }
+      ptrs[k].ptr = 0;
+
+      /* Let's determine the expected partition id, to see
+       * if we already have a transaction for this partition
+       */
+      Uint32 hashVal;
+      if (Ndb::computeHash(&hashVal,
+                           tab,
+                           ptrs))
+      {
+        g_info << "Failed to compute hash" << endl;
+        return NDBT_FAILED;
+      }
+
+
+      partId= tab->getPartitionId(hashVal);
+      pInfo= &partInfo[partId];
+      
+      if (pInfo->ops == NULL)
+      {
+        g_info << "Starting transaction and scan for partition " 
+               << partId << endl;
+        NdbTransaction* pTrans= p_ndb->startTransaction(tab, ptrs);
+        
+        // Swap above line with this one to test that testcase fails
+        // if there's a partitioning bug
+        // NdbTransaction* pTrans= p_ndb->startTransaction();
+        if (!pTrans)
+        {
+          ERR(p_ndb->getNdbError());
+          result= NDBT_FAILED;
+          break;
+        }
+
+        ops= new HugoOperations(*tab, idx);
+        ops->setTransaction(pTrans);
+        pInfo->ops= ops;
+        pInfo->opRowCount= 1;
+      }
+      else
+      {
+        ops= pInfo->ops;
+        pInfo->opRowCount++;
+      }
+    }
+    else
+    {
+      /* User defined partitioning
+       * For Hugo, we can calculate the partition id from 
+       * the record number and the number of partitions
+       */
+      partId= i % tab->getFragmentCount();
+      //printf("Setting Transaction partition Id to %d\n", partId);
+      pInfo= &partInfo[partId];
+      
+      if (pInfo->ops == NULL)
+      {
+        g_info << "No transaction for partition " << partId
+               << " starting one." << endl;
+        NdbTransaction* pTrans= p_ndb->startTransaction(tab, partId);
+        // Swap this line with above one to check that testcase fails
+        // if there's a partitioning bug
+        // NdbTransaction* pTrans= p_ndb->startTransaction();
+        if (!pTrans)
+        {
+          ERR(p_ndb->getNdbError());
+          result= NDBT_FAILED;
+          break;
+        }
+
+        ops= new HugoOperations(*tab, idx);
+        ops->setTransaction(pTrans);
+        pInfo->ops= ops;
+        pInfo->opRowCount= 1;
+      }
+      else
+      {
+        ops= pInfo->ops;
+        pInfo->opRowCount++;
+      }
+    }
+
+    /* Because we pass an Ordered index to the HugoOperations
+     * constructor, pkReadRecord will use an index scan on the 
+     * Ordered index
+     * Despite it's name, it will actually perform index scans
+     * as there is an index.
+     * Error 8050 will cause an NDBD assertion failure in 
+     * Dbtc::execDIGETPRIMCONF() if TC needs to scan a fragment
+     * which is not on the TC node
+     * So for this testcase to pass with no failures we need transaction
+     * hinting and scan partition pruning on equal() to work 
+     * correctly.
+     */
+    if(ops->pkReadRecord(p_ndb, i, 1, NdbOperation::LM_Read, 
+                         true) // Add read to scan
+       != NDBT_OK)
+    {
+      result = NDBT_FAILED;
+      break;
+    }
+
+    if (pInfo->opRowCount == MAX_ROWS_PER_SCAN)
+    {
+      g_info << "Executing scan defined for partition " << partId
+             << " as it has " << MAX_ROWS_PER_SCAN << " ranges defined."
+             << endl;
+      
+      /* This scan has enough rows, let's execute it now */
+      if (exec_scan_read(p_ndb, ops, &dummy, pInfo->opRowCount) != NDBT_OK)
+      {
+        g_info << "Scan exec failed" << endl;
+        result= NDBT_FAILED;
+        break;
+      }
+      
+      /* Clear scan op info for this partition */
+      delete(pInfo->ops);
+      pInfo->ops= NULL;
+      pInfo->opRowCount= 0;
+    }
+
+  }
+
+
+  /* Ok, now we've defined a number of range scans, each with
+   * a number of bounds, time to try executing them
+   * If any of them access a node other than the one they're 
+   * hinted to (i.e. if the pruning and hinting is incorrect)
+   * then the node will fail and the testcase will fail.
+   */
+  if (result == NDBT_OK)
+  {
+    for (Uint32 part=0; part < MAX_PARTITIONS; part++)
+    {
+      PartInfo* pInfo= &partInfo[part];
+      HugoOperations* ops= pInfo->ops;
+      
+      if (ops)
+      {
+        g_info << "Executing scan defined for partition " << part << endl;
+        
+        if (exec_scan_read(p_ndb, ops, &dummy, pInfo->opRowCount) != NDBT_OK)
+        {
+          g_info << "Scan exec failed" << endl;
+          result= NDBT_FAILED;
+          break;
+        }
+        
+        ops->closeTransaction(p_ndb);
+      }
+    }
+  }
+
+  /* Delete all used HugoOperations structures */
+  for (Uint32 i= 0; i < MAX_PARTITIONS; i++)
+    delete(partInfo[i].ops);
+
+  restarter.insertErrorInAllNodes(0);
+  return result;
+}
+
 
 NDBT_TESTSUITE(testPartitioning);
 TESTCASE("pk_dk", 
@@ -543,7 +915,7 @@ TESTCASE("pk_dk", 
   INITIALIZER(run_drop_table);
 }
 TESTCASE("hash_index_dk", 
-	 "Unique index operatations with distribution key")
+	 "Unique index operations with distribution key")
 {
   TC_PROPERTY("distributionkey", ~0);
   TC_PROPERTY("OrderedIndex", (unsigned)0);
@@ -555,7 +927,7 @@ TESTCASE("hash_index_dk", 
   INITIALIZER(run_drop_table);
 }
 TESTCASE("ordered_index_dk", 
-	 "Ordered index operatations with distribution key")
+	 "Ordered index operations with distribution key")
 {
   TC_PROPERTY("distributionkey", (unsigned)1);
   TC_PROPERTY("OrderedIndex", (unsigned)1);
@@ -567,7 +939,7 @@ TESTCASE("ordered_index_dk", 
   INITIALIZER(run_drop_table);
 }
 TESTCASE("smart_scan", 
-	 "Ordered index operatations with distribution key")
+	 "Ordered index operations with distribution key")
 {
   TC_PROPERTY("OrderedIndex", (unsigned)1);
   INITIALIZER(run_drop_table);
@@ -580,6 +952,7 @@ TESTCASE("smart_scan", 
 TESTCASE("startTransactionHint", 
 	 "Test startTransactionHint wo/ distribution key")
 {
+  /* If hint is incorrect, node failure occurs */
   TC_PROPERTY("distributionkey", (unsigned)0);
   INITIALIZER(run_drop_table);
   INITIALIZER(run_create_table);
@@ -589,6 +962,7 @@ TESTCASE("startTransactionHint", 
 TESTCASE("startTransactionHint_dk", 
 	 "Test startTransactionHint with distribution key")
 {
+  /* If hint is incorrect, node failure occurs */
   TC_PROPERTY("distributionkey", (unsigned)~0);
   INITIALIZER(run_drop_table);
   INITIALIZER(run_create_table);
@@ -598,6 +972,7 @@ TESTCASE("startTransactionHint_dk", 
 TESTCASE("startTransactionHint_orderedIndex",
          "Test startTransactionHint and ordered index reads")
 {
+  /* If hint is incorrect, node failure occurs */
   TC_PROPERTY("distributionkey", (unsigned)0);
   TC_PROPERTY("OrderedIndex", (unsigned)1);
   INITIALIZER(run_drop_table);
@@ -610,6 +985,7 @@ TESTCASE("startTransactionHint_orderedIn
 TESTCASE("startTransactionHint_orderedIndex_dk",
          "Test startTransactionHint and ordered index reads with distribution key")
 {
+  /* If hint is incorrect, node failure occurs */
   TC_PROPERTY("distributionkey", (unsigned)~0);
   TC_PROPERTY("OrderedIndex", (unsigned)1);
   INITIALIZER(run_drop_table);
@@ -619,6 +995,74 @@ TESTCASE("startTransactionHint_orderedIn
   INITIALIZER(run_create_pk_index_drop);
   INITIALIZER(run_drop_table);
 }
+TESTCASE("pk_userDefined",
+         "Test primary key operations on table with user-defined partitioning")
+{
+  /* Check PK ops against user-defined partitioned table */
+  INITIALIZER(run_drop_table);
+  INITIALIZER(run_create_table_udefpart);
+  INITIALIZER(run_create_pk_index);
+  INITIALIZER(run_pk_dk);
+  INITIALIZER(run_create_pk_index_drop);
+  INITIALIZER(run_drop_table);
+};
+TESTCASE("hash_index_userDefined",
+         "Unique index operations on table with user-defined partitioning")
+{
+  /* Check hash index ops against user-defined partitioned table */
+  TC_PROPERTY("OrderedIndex", (unsigned)0);
+  INITIALIZER(run_drop_table);
+  INITIALIZER(run_create_table_udefpart);
+  INITIALIZER(run_create_pk_index);
+  INITIALIZER(run_index_dk);
+  INITIALIZER(run_create_pk_index_drop);
+  INITIALIZER(run_drop_table);
+}
+TESTCASE("ordered_index_userDefined", 
+	 "Ordered index operations on table with user-defined partitioning")
+{
+  /* Check ordered index operations against user-defined partitioned table */
+  TC_PROPERTY("OrderedIndex", (unsigned)1);
+  INITIALIZER(run_drop_table);
+  INITIALIZER(run_create_table_udefpart);
+  INITIALIZER(run_create_pk_index);
+  INITIALIZER(run_index_dk);
+  INITIALIZER(run_create_pk_index_drop);
+  INITIALIZER(run_drop_table);
+}
+TESTCASE("startTransactionHint_orderedIndex_userDefined",
+         "Test startTransactionHint and ordered index reads for userDefined partitioned table")
+{
+  TC_PROPERTY("OrderedIndex", (unsigned)1);
+  INITIALIZER(run_drop_table);
+  INITIALIZER(run_create_table_udefpart);
+  INITIALIZER(run_create_pk_index);
+  INITIALIZER(run_startHint_ordered_index);
+  INITIALIZER(run_create_pk_index_drop);
+  INITIALIZER(run_drop_table);
+}
+TESTCASE("startTransactionHint_orderedIndex_mrr",
+         "Test startTransactionHint and ordered index mrr for native partitioned tables")
+{
+  TC_PROPERTY("OrderedIndex", (unsigned)1);
+  INITIALIZER(run_drop_table);
+  INITIALIZER(run_create_table);
+  INITIALIZER(run_create_pk_index);
+  INITIALIZER(run_startHint_ordered_index_mrr);
+  INITIALIZER(run_create_pk_index_drop);
+  INITIALIZER(run_drop_table);
+}
+TESTCASE("startTransactionHint_orderedIndex_mrr_userDefined",
+         "Test startTransactionHint and ordered index mrr for user-defined partitioned tables")
+{
+  TC_PROPERTY("OrderedIndex", (unsigned)1);
+  INITIALIZER(run_drop_table);
+  INITIALIZER(run_create_table_udefpart);
+  INITIALIZER(run_create_pk_index);
+  INITIALIZER(run_startHint_ordered_index_mrr);
+  INITIALIZER(run_create_pk_index_drop);
+  INITIALIZER(run_drop_table);
+}
 
 NDBT_TESTSUITE_END(testPartitioning);
 

=== modified file 'storage/ndb/test/src/HugoOperations.cpp'
--- a/storage/ndb/test/src/HugoOperations.cpp	2008-11-17 09:28:34 +0000
+++ b/storage/ndb/test/src/HugoOperations.cpp	2009-02-17 19:12:06 +0000
@@ -69,14 +69,31 @@ NdbConnection* HugoOperations::getTransa
 int HugoOperations::pkReadRecord(Ndb* pNdb,
 				 int recordNo,
 				 int numRecords,
-				 NdbOperation::LockMode lm){
+				 NdbOperation::LockMode lm,
+                                 bool addToScan){
   int a;  
-  allocRows(numRecords);
-  indexScans.clear();
+  
+  NdbOperation* pOp = 0;
+  bool firstRead= true;
+
+  if (addToScan && (pIndexScanOp != NULL))
+  {
+    /* We're adding reads as scan ranges, and the 
+     * scan exists already
+     */
+    pOp= pIndexScanOp;
+    firstRead= false;
+  }
+  else
+  {
+    /* Normal path */
+    allocRows(numRecords);
+    indexScans.clear();
+    pIndexScanOp = 0;
+  }
+    
   int check;
 
-  NdbOperation* pOp = 0;
-  pIndexScanOp = 0;
 
   for(int r=0; r < numRecords; r++){
     
@@ -95,13 +112,17 @@ rand_lock_mode:
     case NdbOperation::LM_Exclusive:
     case NdbOperation::LM_CommittedRead:
     case NdbOperation::LM_SimpleRead:
-      if(idx && idx->getType() == NdbDictionary::Index::OrderedIndex && 
-	 pIndexScanOp == 0)
+      if(idx && idx->getType() == NdbDictionary::Index::OrderedIndex)
       {
-	pIndexScanOp = ((NdbIndexScanOperation*)pOp);
-	check = pIndexScanOp->readTuples(lm);
-        /* Record NdbIndexScanOperation ptr for later... */
-        indexScans.push_back(pIndexScanOp);
+        if (pIndexScanOp == 0)
+        {
+          pIndexScanOp = ((NdbIndexScanOperation*)pOp);
+          bool mrrScan= (numRecords > 1) || addToScan;
+          Uint32 flags= mrrScan? NdbScanOperation::SF_MultiRange : 0; 
+          check = pIndexScanOp->readTuples(lm, flags);
+          /* Record NdbIndexScanOperation ptr for later... */
+          indexScans.push_back(pIndexScanOp);
+        }
       }
       else
 	check = pOp->readTuple(lm);
@@ -120,10 +141,32 @@ rand_lock_mode:
     if (equalForRow(pOp, r+recordNo) != 0)
       return NDBT_FAILED;
 
+    Uint32 partId;
+    Ndb::PartitionSpec pSpec;
+    Ndb::PartitionSpec* pSpecPtr= NULL;
+    pSpec.type= Ndb::PartitionSpec::PS_NONE;
+
+    /* Do we need to set the partitionId for this operation? */
+    if (getPartIdForRow(pOp, r+recordNo, partId))
+    {
+      if (pIndexScanOp)
+      {
+        pSpecPtr= &pSpec;
+        pSpec.type= Ndb::PartitionSpec::PS_USER_DEFINED;
+        pSpec.UserDefined.partitionId= partId;
+      }
+      else
+      {
+        g_info << "Setting operation partition Id" << endl;
+        pOp->setPartitionId(partId);
+      }
+    }
+
     if(pIndexScanOp)
-      pIndexScanOp->end_of_bound(r);
-    
-    if(r == 0 || pIndexScanOp == 0)
+      pIndexScanOp->end_of_bound(r, pSpecPtr, sizeof(pSpec));
+
+    if(pIndexScanOp == 0 ||      // PK op 
+       ((r == 0) && firstRead))  // First read of scan
     {
       // Define attributes to read  
       for(a = 0; a<tab.getNoOfColumns(); a++){
@@ -192,13 +235,36 @@ rand_lock_mode:
       return NDBT_FAILED;
     }
     
+    int rowid= rand() % records;
+
     // Define primary keys
-    if (equalForRow(pOp, rand() % records) != 0)
+    if (equalForRow(pOp, rowid) != 0)
       return NDBT_FAILED;
 
+    Uint32 partId;
+    Ndb::PartitionSpec pSpec;
+    Ndb::PartitionSpec* pSpecPtr= NULL;
+    pSpec.type= Ndb::PartitionSpec::PS_NONE;
+
+    /* Do we need to set the partitionId for this operation? */
+    if (getPartIdForRow(pOp, rowid, partId))
+    {
+      if (pIndexScanOp)
+      {
+        pSpecPtr= &pSpec;
+        pSpec.type= Ndb::PartitionSpec::PS_USER_DEFINED;
+        pSpec.UserDefined.partitionId= partId;
+      }
+      else
+      {
+        g_info << "Setting operation partition Id" << endl;
+        pOp->setPartitionId(partId);
+      }
+    }
+
     if(pIndexScanOp)
-      pIndexScanOp->end_of_bound(r);
-    
+      pIndexScanOp->end_of_bound(r, pSpecPtr, sizeof(pSpec));
+
     if(r == 0 || pIndexScanOp == 0)
     {
       // Define attributes to read  
@@ -241,6 +307,11 @@ int HugoOperations::pkUpdateRecord(Ndb* 
     {
       return NDBT_FAILED;
     }
+
+    Uint32 partId;
+    if(getPartIdForRow(pOp, r+recordNo, partId))
+      pOp->setPartitionId(partId);
+    
   }
   return NDBT_OK;
 }
@@ -288,6 +359,11 @@ int HugoOperations::pkInsertRecord(Ndb* 
     {
       return NDBT_FAILED;
     }
+
+    Uint32 partId;
+    if(getPartIdForRow(pOp, r+recordNo, partId))
+      pOp->setPartitionId(partId);
+    
   }
   return NDBT_OK;
 }
@@ -315,6 +391,11 @@ int HugoOperations::pkWriteRecord(Ndb* p
     if (equalForRow(pOp, r+recordNo) != 0)
       return NDBT_FAILED;
     
+    Uint32 partId;
+    if(getPartIdForRow(pOp, r+recordNo, partId))
+      pOp->setPartitionId(partId);
+    
+
     // Define attributes to update
     for(a = 0; a<tab.getNoOfColumns(); a++){
       if (tab.getColumn(a)->getPrimaryKey() == false){
@@ -349,6 +430,11 @@ int HugoOperations::pkWritePartialRecord
     // Define primary keys
     if (equalForRow(pOp, r+recordNo) != 0)
       return NDBT_FAILED;
+
+    Uint32 partId;
+    if(getPartIdForRow(pOp, r+recordNo, partId))
+      pOp->setPartitionId(partId);
+    
   }
   return NDBT_OK;
 }
@@ -374,6 +460,10 @@ int HugoOperations::pkDeleteRecord(Ndb* 
     // Define primary keys
     if (equalForRow(pOp, r+recordNo) != 0)
       return NDBT_FAILED;
+
+    Uint32 partId;
+    if(getPartIdForRow(pOp, r+recordNo, partId))
+      pOp->setPartitionId(partId);
   }
   return NDBT_OK;
 }
@@ -397,7 +487,7 @@ int HugoOperations::execute_Commit(Ndb* 
     return err.code;
   }
 
-  for(int i = 0; i<m_result_sets.size(); i++){
+  for(unsigned int i = 0; i<m_result_sets.size(); i++){
     m_executed_result_sets.push_back(m_result_sets[i]);
 
     int rows = m_result_sets[i].records;
@@ -449,7 +539,7 @@ int HugoOperations::execute_NoCommit(Ndb
     return err.code;
   }
 
-  for(int i = 0; i<m_result_sets.size(); i++){
+  for(unsigned int i = 0; i<m_result_sets.size(); i++){
     m_executed_result_sets.push_back(m_result_sets[i]);
 
     int rows = m_result_sets[i].records;
@@ -562,6 +652,7 @@ HugoOperations::wait_async(Ndb* pNdb, in
 HugoOperations::HugoOperations(const NdbDictionary::Table& _tab,
 			       const NdbDictionary::Index* idx):
   UtilTransactions(_tab, idx),
+  pIndexScanOp(NULL),
   calc(_tab)
 {
 }
@@ -589,13 +680,41 @@ HugoOperations::equalForRow(NdbOperation
       }
     }
   }
+
   return NDBT_OK;
 }
 
+bool HugoOperations::getPartIdForRow(const NdbOperation* pOp,
+                                     int rowid,
+                                     Uint32& partId)
+{
+  if (tab.getFragmentType() == NdbDictionary::Object::UserDefined)
+  {
+    /* Primary keys and Ordered indexes are partitioned according
+     * to the row number
+     */
+    if (pOp->getType() != NdbOperation::UniqueIndexAccess)
+    {
+      /* Need to set the partitionId for this op
+       * For Hugo, we use 'HASH' partitioning, which is probably
+       * better called 'MODULO' partitioning with
+       * FragId == rowNum % NumPartitions
+       * This gives a good balance with the normal Hugo data, but different
+       * row to partition assignments than normal key partitioning.
+       */
+      const Uint32 numFrags= tab.getFragmentCount();
+      partId= rowid % numFrags;
+      g_info << "Returning partition Id of " << partId << endl;
+      return true;
+    }
+  }
+  partId= ~0;
+  return false;
+}
+
 int HugoOperations::equalForAttr(NdbOperation* pOp,
 				   int attrId, 
 				   int rowId){
-  int check = -1;
   const NdbDictionary::Column* attr = tab.getColumn(attrId);  
   if (attr->getPrimaryKey() == false){
     g_info << "Can't call equalForAttr on non PK attribute" << endl;
@@ -614,7 +733,6 @@ int HugoOperations::setValueForAttr(NdbO
 				      int attrId, 
 				      int rowId,
 				      int updateId){
-  int check = -1;
   const NdbDictionary::Column* attr = tab.getColumn(attrId);     
   
   int len = attr->getSizeInBytes();

=== modified file 'storage/ndb/test/src/HugoTransactions.cpp'
--- a/storage/ndb/test/src/HugoTransactions.cpp	2009-02-04 12:40:39 +0000
+++ b/storage/ndb/test/src/HugoTransactions.cpp	2009-02-17 19:12:06 +0000
@@ -1080,13 +1080,19 @@ HugoTransactions::pkUpdateRecords(Ndb* p
           do {
             
             if (calc.verifyRowValues(rows[0]) != 0){
+              g_info << "Row validation failure" << endl;
               closeTransaction(pNdb);
               return NDBT_FAILED;
             }
             
             int updates = calc.getUpdatesValue(rows[0]) + 1;
             
-            if(pkUpdateRecord(pNdb, r+rows_found, 1, updates) != NDBT_OK)
+            /* Rows may not arrive in the order they were requested
+             * (When multiple partitions scanned without ORDERBY)
+             * therefore we use the id from the row to update it
+             */
+            const Uint32 rowId= calc.getIdValue(rows[0]);
+            if(pkUpdateRecord(pNdb, rowId, 1, updates) != NDBT_OK)
             {
               ERR(pTrans->getNdbError());
               closeTransaction(pNdb);
@@ -1103,6 +1109,7 @@ HugoTransactions::pkUpdateRecords(Ndb* p
 
         if(check != 1)
         {
+          g_info << "Check failed" << endl;
           closeTransaction(pNdb);
           return NDBT_FAILED;
         } 
@@ -1110,6 +1117,8 @@ HugoTransactions::pkUpdateRecords(Ndb* p
 
       if (rows_found != batch)
       {
+        g_info << "Incorrect num of rows found.  Expected "
+               << batch << ". Found " << rows_found << endl;
         closeTransaction(pNdb);
         return NDBT_FAILED;
       }

-- 
MySQL Code Commits Mailing List
For list archives: http://lists.mysql.com/commits
To unsubscribe:    http://lists.mysql.com/commits?unsub=commits@bugs.mysql.com
[4 Mar 2009 13:24] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/68241

2898 Frazer Clement	2009-03-04
      Bug#37934 Partition pruning doesn't work for all queries in DBT2
      
      Per-range partitioning information is added to the IndexBound API.
      Each bound can have individual partition specification.
      If all bounds in a Multi Range Read Scan have the same partitioning
      information then the MRR scan can be pruned to a single partition.
      This also applies to partitioning information determined internally
      by the NDBAPI - e.g. when NDBAPI determines that a range is bounded
      by a single value of the table's distribution keys.  If all ranges
      in an MRR scan are similarly bounded, the MRR scan will be 
      automatically pruned.
      
      When UserDefined partitioning schemes are used, ha_ndbcluster.cc 
      now passes explicit per-range partitioning information down to 
      NDBAPI.
      
      storage/ndb/testPartitioning is extended to improve support for
      UserDefined partitioned tables and MRR scans where all range bounds
      are contained within a single table partition.
      
      This work also builds on some Blob part partitioning fixes made in
      Bug#34268.
      added:
        mysql-test/suite/ndb/r/ndb_partition_hash.result
        mysql-test/suite/ndb/t/ndb_partition_hash.test
      modified:
        sql/ha_ndbcluster.cc
        storage/ndb/include/ndbapi/Ndb.hpp
        storage/ndb/include/ndbapi/NdbIndexScanOperation.hpp
        storage/ndb/include/ndbapi/NdbOperation.hpp
        storage/ndb/include/ndbapi/NdbScanOperation.hpp
        storage/ndb/src/ndbapi/Ndb.cpp
        storage/ndb/src/ndbapi/NdbOperationSearch.cpp
        storage/ndb/src/ndbapi/NdbScanOperation.cpp
        storage/ndb/src/ndbapi/ndberror.c
        storage/ndb/test/include/HugoOperations.hpp
        storage/ndb/test/ndbapi/testPartitioning.cpp
        storage/ndb/test/src/HugoOperations.cpp
        storage/ndb/test/src/HugoTransactions.cpp
[6 Mar 2009 18:07] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/68534

2898 Frazer Clement	2009-03-06
      Bug#37934 Partition pruning doesn't work for all queries in DBT2
      
      Per-range partitioning information is added to the IndexBound API.
      Each bound can have individual partition specification.
      If all bounds in a Multi Range Read Scan have the same partitioning
      information then the MRR scan can be pruned to a single partition.
      This also applies to partitioning information determined internally
      by the NDBAPI - e.g. when NDBAPI determines that a range is bounded
      by a single value of the table's distribution keys.  If all ranges
      in an MRR scan are similarly bounded, the MRR scan will be 
      automatically pruned.
      
      When UserDefined partitioning schemes are used, ha_ndbcluster.cc 
      now passes explicit per-range partitioning information down to 
      NDBAPI.
      
      storage/ndb/testPartitioning is extended to improve support for
      UserDefined partitioned tables and MRR scans where all range bounds
      are contained within a single table partition.
      
      This work also builds on some Blob part partitioning fixes made in
      Bug#34268.
      added:
        mysql-test/suite/ndb/r/ndb_partition_hash.result
        mysql-test/suite/ndb/t/ndb_partition_hash.test
      modified:
        sql/ha_ndbcluster.cc
        storage/ndb/include/ndbapi/Ndb.hpp
        storage/ndb/include/ndbapi/NdbIndexScanOperation.hpp
        storage/ndb/include/ndbapi/NdbOperation.hpp
        storage/ndb/include/ndbapi/NdbScanOperation.hpp
        storage/ndb/src/ndbapi/Ndb.cpp
        storage/ndb/src/ndbapi/NdbOperationSearch.cpp
        storage/ndb/src/ndbapi/NdbScanOperation.cpp
        storage/ndb/src/ndbapi/ndberror.c
        storage/ndb/test/include/HugoOperations.hpp
        storage/ndb/test/ndbapi/testPartitioning.cpp
        storage/ndb/test/src/HugoOperations.cpp
        storage/ndb/test/src/HugoTransactions.cpp
[9 Mar 2009 13:23] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/68644

2920 Frazer Clement	2009-03-09
      Bug#37934 Partition pruning doesn't work for all queries in DBT2
      
      Modifications to 6.3 fix for 6.4 :
       1) Make partition setting mechanisms more strict about
          setting partitionIds for natively partitioned tables
       2) Add DISTR_KEY_RECORD variant to PartitionSpec structure
       3) Add testing of 2)
      
      With this patch, ha_ndbcluster.cc only provides explicit 
      partition ids for UserDefined partitioned tables.
      modified:
        sql/ha_ndbcluster.cc
        storage/ndb/include/ndbapi/Ndb.hpp
        storage/ndb/include/ndbapi/NdbScanOperation.hpp
        storage/ndb/src/ndbapi/Ndb.cpp
        storage/ndb/src/ndbapi/NdbOperationDefine.cpp
        storage/ndb/src/ndbapi/NdbOperationSearch.cpp
        storage/ndb/src/ndbapi/NdbScanOperation.cpp
        storage/ndb/src/ndbapi/ndberror.c
        storage/ndb/test/ndbapi/testPartitioning.cpp
[9 Mar 2009 13:31] Bugs System
Pushed into 5.1.32-ndb-6.4.4 (revid:frazer@mysql.com-20090309132209-2bydx246vafvn1y4) (version source revid:frazer@mysql.com-20090309132209-2bydx246vafvn1y4) (merge vers: 5.1.32-ndb-6.4.4) (pib:6)
[9 Mar 2009 13:31] Bugs System
Pushed into 5.1.32-ndb-6.3.24 (revid:frazer@mysql.com-20090306180617-l2ftm0f8m93g9qtm) (version source revid:frazer@mysql.com-20090306180617-l2ftm0f8m93g9qtm) (merge vers: 5.1.32-ndb-6.3.24) (pib:6)
[9 Mar 2009 13:47] Frazer Clement
Fix pushed : 
6th March - telco-6.3 fix
9th March - telco-6.4 fix

NdbApi can now prune MRR scans correctly iff all ranges can be pruned to the same partitionid/ distribution key hash value.
A new accessor is added (NdbScanOperation::getPruned()) which can be used from Api users to determine whether a scan is pruned or not.
This functionality is now tested using extensions to testPartitioning to verify MRR scans, and UserDefined partitioning in general, as well as cycling through the various native partitioning schemes (Key, Linear Key, HashMap).

This bug fix builds on the changes made to UserDefined table Blob part partitioning made in bug#43268.

Note that in the 6.4 code, the MySQLD partitioning layer dictates which partition to access *only* for UserDefined partitioned tables (PARTITION BY HASH(), LIST(), RANGE()).  For natively partitioned tables (KEY()), NdbApi always chooses which partitions to access or scan based on distribution key values passed.
If necessary in future, MySQLD/ha_ndbcluster can prune scans which are not natively pruned by supplying suitable key information via the new Ndb::PartitionSpec structure (e.g. if scanning an index which does not contain distribution keys, but it is known that all relevant results have fixed distribution key values).

Also, in future, NdbApi may allow a single scan to scan a subset of all partitions (currently either one or all can be scanned).
[14 Mar 2009 15:45] Jon Stephens
Documented bugfix in the NDB-6.3.24 and 7.0.4 changelogs as follows:

        Partition pruning did not work correctly for queries involving multiple
        range scans.

        As part of the fix for this issue, several improvements have been 
        made in the NDB API, including the addition of a new
        NdbScanOperation::getPruned() method, a new variant of
        NdbIndexScanOperation::setBound(), and a new
        Ndb::PartitionSpec data structure. For more
        information about these changes, see ... .

Also documented the changes/additions in the Cluster API documentation.