List:Commits« Previous MessageNext Message »
From:Jonas Oreland Date:May 31 2011 8:29am
Subject:bzr push into mysql-5.1-telco-7.0 branch (jonas:4427 to 4428)
View as plain text  
 4428 Jonas Oreland	2011-05-31 [merge]
      ndb - wl4124 - kernel part

    added:
      storage/ndb/include/kernel/signaldata/IndexStatSignal.hpp
      storage/ndb/src/common/debugger/signaldata/IndexStatSignal.cpp
    modified:
      storage/ndb/include/kernel/AttributeHeader.hpp
      storage/ndb/include/kernel/GlobalSignalNumbers.h
      storage/ndb/include/kernel/ndb_limits.h
      storage/ndb/include/kernel/signaldata/AccScan.hpp
      storage/ndb/include/kernel/signaldata/ScanFrag.hpp
      storage/ndb/include/kernel/signaldata/SignalData.hpp
      storage/ndb/include/kernel/signaldata/SumaImpl.hpp
      storage/ndb/include/kernel/signaldata/TuxBound.hpp
      storage/ndb/include/kernel/signaldata/TuxContinueB.hpp
      storage/ndb/include/kernel/signaldata/UtilRelease.hpp
      storage/ndb/include/mgmapi/mgmapi_config_parameters.h
      storage/ndb/include/ndb_constants.h
      storage/ndb/src/common/debugger/signaldata/CMakeLists.txt
      storage/ndb/src/common/debugger/signaldata/Makefile.am
      storage/ndb/src/common/debugger/signaldata/ScanFrag.cpp
      storage/ndb/src/common/debugger/signaldata/SchemaTransImpl.cpp
      storage/ndb/src/common/debugger/signaldata/SignalDataPrint.cpp
      storage/ndb/src/common/debugger/signaldata/SignalNames.cpp
      storage/ndb/src/kernel/blocks/ERROR_codes.txt
      storage/ndb/src/kernel/blocks/cmvmi/Cmvmi.cpp
      storage/ndb/src/kernel/blocks/dbdict/Dbdict.cpp
      storage/ndb/src/kernel/blocks/dbdict/Dbdict.hpp
      storage/ndb/src/kernel/blocks/dblqh/Dblqh.hpp
      storage/ndb/src/kernel/blocks/dblqh/DblqhMain.cpp
      storage/ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp
      storage/ndb/src/kernel/blocks/dbtux/Dbtux.hpp
      storage/ndb/src/kernel/blocks/dbtux/DbtuxBuild.cpp
      storage/ndb/src/kernel/blocks/dbtux/DbtuxDebug.cpp
      storage/ndb/src/kernel/blocks/dbtux/DbtuxGen.cpp
      storage/ndb/src/kernel/blocks/dbtux/DbtuxMaint.cpp
      storage/ndb/src/kernel/blocks/dbtux/DbtuxProxy.cpp
      storage/ndb/src/kernel/blocks/dbtux/DbtuxProxy.hpp
      storage/ndb/src/kernel/blocks/dbtux/DbtuxScan.cpp
      storage/ndb/src/kernel/blocks/dbtux/DbtuxStat.cpp
      storage/ndb/src/kernel/blocks/dbtux/DbtuxTree.cpp
      storage/ndb/src/kernel/blocks/suma/Suma.cpp
      storage/ndb/src/kernel/blocks/suma/Suma.hpp
      storage/ndb/src/kernel/blocks/trix/Trix.cpp
      storage/ndb/src/kernel/blocks/trix/Trix.hpp
      storage/ndb/src/mgmsrv/ConfigInfo.cpp
      storage/ndb/src/ndbapi/ndberror.c
      storage/ndb/tools/restore/Restore.cpp
 4427 Jonas Oreland	2011-05-30
      ndb - kill warnings for gcc 4.4.3

    modified:
      storage/ndb/src/kernel/blocks/dblqh/DblqhMain.cpp
      storage/ndb/src/kernel/blocks/dbtc/DbtcMain.cpp
      storage/ndb/src/kernel/blocks/dbtup/DbtupCommit.cpp
      storage/ndb/src/kernel/blocks/dbtux/DbtuxDebug.cpp
      storage/ndb/src/kernel/blocks/dbtux/DbtuxGen.cpp
      storage/ndb/src/kernel/blocks/dbtux/DbtuxScan.cpp
      storage/ndb/src/kernel/blocks/dbutil/DbUtil.cpp
      storage/ndb/src/kernel/blocks/suma/Suma.cpp
=== modified file 'storage/ndb/include/kernel/AttributeHeader.hpp'
--- a/storage/ndb/include/kernel/AttributeHeader.hpp	2011-05-17 23:29:55 +0000
+++ b/storage/ndb/include/kernel/AttributeHeader.hpp	2011-05-25 15:03:11 +0000
@@ -84,6 +84,10 @@ public:
   STATIC_CONST( OPTIMIZE_MOVE_VARPART = 0x0001 ); //option to move varpart
   STATIC_CONST( OPTIMIZE_MOVE_FIXPART = 0x0002 ); //option to move fixpart
 
+  // index stats pseudo columns
+  STATIC_CONST( INDEX_STAT_KEY   = 0xFFD0 );
+  STATIC_CONST( INDEX_STAT_VALUE = 0xFFD1 );
+
   // NOTE: in 5.1 ctors and init take size in bytes
 
   /** Initialize AttributeHeader at location aHeaderPtr */

=== modified file 'storage/ndb/include/kernel/GlobalSignalNumbers.h'
--- a/storage/ndb/include/kernel/GlobalSignalNumbers.h	2011-04-28 07:47:53 +0000
+++ b/storage/ndb/include/kernel/GlobalSignalNumbers.h	2011-05-19 09:16:32 +0000
@@ -866,15 +866,6 @@ extern const GlobalSignalNumber NO_OF_SI
 #define GSN_648
 #define GSN_649
 
-#define GSN_650
-#define GSN_651
-#define GSN_652
-#define GSN_653
-#define GSN_654
-#define GSN_655
-
-#define GSN_656
-
 #define GSN_UTIL_CREATE_LOCK_REQ        132
 #define GSN_UTIL_CREATE_LOCK_REF        133
 #define GSN_UTIL_CREATE_LOCK_CONF       188
@@ -1079,4 +1070,12 @@ extern const GlobalSignalNumber NO_OF_SI
 #define GSN_RELEASE_PAGES_REQ           680
 #define GSN_RELEASE_PAGES_CONF          681
 
+#define GSN_INDEX_STAT_REQ              650
+#define GSN_INDEX_STAT_CONF             651
+#define GSN_INDEX_STAT_REF              652
+#define GSN_INDEX_STAT_IMPL_REQ         653
+#define GSN_INDEX_STAT_IMPL_CONF        654
+#define GSN_INDEX_STAT_IMPL_REF         655
+#define GSN_INDEX_STAT_REP              656
+
 #endif

=== modified file 'storage/ndb/include/kernel/ndb_limits.h'
--- a/storage/ndb/include/kernel/ndb_limits.h	2011-05-26 11:49:47 +0000
+++ b/storage/ndb/include/kernel/ndb_limits.h	2011-05-29 10:55:32 +0000
@@ -219,4 +219,19 @@
  */
 #define NDB_SPJ_MAX_TREE_NODES 32
 
+/*
+ * Stored ordered index stats uses 2 Longvarbinary pseudo-columns: the
+ * packed index keys and the packed values.  Key size is limited by
+ * SAMPLES table which has 3 other PK attributes.  Also length bytes is
+ * counted as 1 word.  Values currently contain RIR (one word) and RPK
+ * (one word for each key level).  The SAMPLEs table STAT_VALUE column
+ * is longer to allow future changes.
+ */
+#define MAX_INDEX_STAT_KEY_COUNT    MAX_ATTRIBUTES_IN_INDEX
+#define MAX_INDEX_STAT_KEY_SIZE     (MAX_KEY_SIZE_IN_WORDS - 3 - 1)
+#define MAX_INDEX_STAT_VALUE_COUNT  (1 + MAX_INDEX_STAT_KEY_COUNT)
+#define MAX_INDEX_STAT_VALUE_SIZE   MAX_INDEX_STAT_VALUE_COUNT
+#define MAX_INDEX_STAT_VALUE_CSIZE  512 /* Longvarbinary(2048) */
+#define MAX_INDEX_STAT_VALUE_FORMAT 1
+
 #endif

=== modified file 'storage/ndb/include/kernel/signaldata/AccScan.hpp'
--- a/storage/ndb/include/kernel/signaldata/AccScan.hpp	2011-02-01 21:05:11 +0000
+++ b/storage/ndb/include/kernel/signaldata/AccScan.hpp	2011-05-19 09:16:32 +0000
@@ -72,6 +72,9 @@ private:
 
   static Uint32 getLcpScanFlag(const Uint32 & requestInfo);
   static void setLcpScanFlag(Uint32 & requestInfo, Uint32 nr);
+
+  static Uint32 getStatScanFlag(const Uint32 & requestInfo);
+  static void setStatScanFlag(Uint32 & requestInfo, Uint32 nr);
 };
 
 /**
@@ -83,10 +86,11 @@ private:
  * d = No disk scan          - 1  Bit 7
  * n = Node recovery scan    - 1  Bit 8
  * c = LCP scan              - 1  Bit 9
+ * s = Statistics scan       - 1  Bit 4
  *
  *           1111111111222222222233
  * 01234567890123456789012345678901
- *   l  hzdn   
+ *   l shzdn   
  */
 #define AS_LOCK_MODE_SHIFT       (2)
 #define AS_LOCK_MODE_MASK        (1)
@@ -95,6 +99,7 @@ private:
 #define AS_NO_DISK_SCAN          (7)
 #define AS_NR_SCAN               (8)
 #define AS_LCP_SCAN              (9)
+#define AS_STAT_SCAN             (4)
 
 inline 
 Uint32
@@ -174,6 +179,19 @@ AccScanReq::setLcpScanFlag(UintR & reque
   requestInfo |= (val << AS_LCP_SCAN);
 }
 
+inline
+Uint32
+AccScanReq::getStatScanFlag(const Uint32 & requestInfo){
+  return (requestInfo >> AS_STAT_SCAN) & 1;
+}
+
+inline
+void
+AccScanReq::setStatScanFlag(UintR & requestInfo, UintR val){
+  ASSERT_BOOL(val, "AccScanReq::setStatScanScanFlag");
+  requestInfo |= (val << AS_STAT_SCAN);
+}
+
 class AccScanConf {
   /**
    * Sender(s)
@@ -212,7 +230,9 @@ class AccScanRef {
 
   enum ErrorCode {
     TuxNoFreeScanOp = 909,
-    TuxIndexNotOnline = 910
+    TuxIndexNotOnline = 910,
+    TuxNoFreeStatOp = 911,
+    TuxInvalidLockMode = 912
   };
 
 public:

=== added file 'storage/ndb/include/kernel/signaldata/IndexStatSignal.hpp'
--- a/storage/ndb/include/kernel/signaldata/IndexStatSignal.hpp	1970-01-01 00:00:00 +0000
+++ b/storage/ndb/include/kernel/signaldata/IndexStatSignal.hpp	2011-05-19 09:16:32 +0000
@@ -0,0 +1,121 @@
+/* Copyright (C) 2003 MySQL AB
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; version 2 of the License.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
+
+#ifndef INDEX_STAT_SIGNAL_DATA_HPP
+#define INDEX_STAT_SIGNAL_DATA_HPP
+
+#include "SignalData.hpp"
+
+struct IndexStatReq {
+  enum RequestType {
+    // update
+    RT_UPDATE_STAT = 1,
+    RT_CLEAN_NEW = 2,
+    RT_SCAN_FRAG = 3,
+    RT_CLEAN_OLD = 4,
+    RT_START_MON = 5,
+    // delete
+    RT_DELETE_STAT = 6,
+    RT_STOP_MON = 7,
+    RT_DROP_HEAD = 8,
+    RT_CLEAN_ALL = 9
+  };
+  STATIC_CONST( SignalLength = 9 );
+  Uint32 clientRef;
+  Uint32 clientData;
+  Uint32 transId;
+  Uint32 transKey;
+  Uint32 requestInfo;
+  Uint32 requestFlag;
+  Uint32 indexId;
+  Uint32 indexVersion;
+  Uint32 tableId;
+};
+
+struct IndexStatImplReq {
+  STATIC_CONST( SignalLength = 10 );
+  Uint32 senderRef;
+  Uint32 senderData;
+  Uint32 transId;
+  Uint32 requestType;
+  Uint32 requestFlag;
+  Uint32 indexId;
+  Uint32 indexVersion;
+  Uint32 tableId;
+  Uint32 fragId;
+  Uint32 fragCount;
+};
+
+struct IndexStatConf {
+  STATIC_CONST( SignalLength = 3 );
+  Uint32 senderRef;
+  union { Uint32 senderData; Uint32 clientData; };
+  Uint32 transId;
+};
+
+struct IndexStatImplConf {
+  STATIC_CONST( SignalLength = 2 );
+  Uint32 senderRef;
+  Uint32 senderData;
+};
+
+struct IndexStatRef {
+  enum ErrorCode {
+    Busy = 701,
+    NotMaster = 702,
+    InvalidIndex = 913,
+    InvalidRequest = 914,
+    NoFreeStatOp = 915,
+    InvalidSysTable = 916,
+    InvalidSysTableData = 917,
+    BusyUtilPrepare = 918,
+    BusyUtilExecute = 919
+  };
+  STATIC_CONST( SignalLength = 7 );
+  Uint32 senderRef;
+  union { Uint32 senderData; Uint32 clientData; };
+  Uint32 transId;
+  Uint32 errorCode;
+  Uint32 errorLine;
+  Uint32 errorNodeId;
+  Uint32 masterNodeId;
+};
+
+struct IndexStatImplRef {
+  STATIC_CONST( SignalLength = 4 );
+  Uint32 senderRef;
+  Uint32 senderData;
+  Uint32 errorCode;
+  Uint32 errorLine;
+};
+
+struct IndexStatRep {
+  enum RequestType {
+    RT_UPDATE_REQ = 1,  // TUX->DICT request stats update
+    RT_UPDATE_CONF = 2  // TRIX->TUX report stats update
+  };
+  STATIC_CONST( SignalLength = 9 );
+  Uint32 senderRef;
+  Uint32 senderData;
+  Uint32 requestType;
+  Uint32 requestFlag;
+  Uint32 indexId;
+  Uint32 indexVersion;
+  Uint32 tableId;
+  Uint32 fragId;
+  Uint32 loadTime;
+};
+
+#endif

=== modified file 'storage/ndb/include/kernel/signaldata/ScanFrag.hpp'
--- a/storage/ndb/include/kernel/signaldata/ScanFrag.hpp	2011-02-23 19:28:26 +0000
+++ b/storage/ndb/include/kernel/signaldata/ScanFrag.hpp	2011-05-19 09:16:32 +0000
@@ -79,6 +79,7 @@ public:
   static Uint32 getScanPrio(const Uint32 & requestInfo);
   static Uint32 getNoDiskFlag(const Uint32 & requestInfo);
   static Uint32 getLcpScanFlag(const Uint32 & requestInfo);
+  static Uint32 getStatScanFlag(const Uint32 & requestInfo);
 
   static void setLockMode(Uint32 & requestInfo, Uint32 lockMode);
   static void setHoldLockFlag(Uint32 & requestInfo, Uint32 holdLock);
@@ -91,6 +92,7 @@ public:
   static void setScanPrio(Uint32& requestInfo, Uint32 prio);
   static void setNoDiskFlag(Uint32& requestInfo, Uint32 val);
   static void setLcpScanFlag(Uint32 & requestInfo, Uint32 val);
+  static void setStatScanFlag(Uint32 & requestInfo, Uint32 val);
 
   static void setReorgFlag(Uint32 & requestInfo, Uint32 val);
   static Uint32 getReorgFlag(const Uint32 & requestInfo);
@@ -270,11 +272,12 @@ public:
  * p = Scan prio             - 4  Bits (12-15) -> max 15
  * r = Reorg flag            - 2  Bits (1-2)
  * C = corr value flag       - 1  Bit  (16)
+ * s = Stat scan             - 1  Bit 17
  *
  *           1111111111222222222233
  * 01234567890123456789012345678901
  *  rrcdlxhkrztppppaaaaaaaaaaaaaaaa   Short variant ( < 6.4.0)
- *  rrcdlxhkrztppppA                  Long variant (6.4.0 +)
+ *  rrcdlxhkrztppppAs                 Long variant (6.4.0 +)
  */
 #define SF_LOCK_MODE_SHIFT   (5)
 #define SF_LOCK_MODE_MASK    (1)
@@ -299,6 +302,8 @@ public:
 
 #define SF_CORR_FACTOR_SHIFT  (16)
 
+#define SF_STAT_SCAN_SHIFT  (17)
+
 inline 
 Uint32
 ScanFragReq::getLockMode(const Uint32 & requestInfo){
@@ -488,6 +493,19 @@ ScanFragReq::setCorrFactorFlag(UintR & r
   requestInfo |= (val << SF_CORR_FACTOR_SHIFT);
 }
 
+inline
+Uint32
+ScanFragReq::getStatScanFlag(const Uint32 & requestInfo){
+  return (requestInfo >> SF_STAT_SCAN_SHIFT) & 1;
+}
+
+inline
+void
+ScanFragReq::setStatScanFlag(UintR & requestInfo, UintR val){
+  ASSERT_BOOL(val, "ScanFragReq::setStatScanFlag");
+  requestInfo |= (val << SF_STAT_SCAN_SHIFT);
+}
+
 /**
  * Request Info (SCAN_NEXTREQ)
  *

=== modified file 'storage/ndb/include/kernel/signaldata/SignalData.hpp'
--- a/storage/ndb/include/kernel/signaldata/SignalData.hpp	2011-04-09 15:48:21 +0000
+++ b/storage/ndb/include/kernel/signaldata/SignalData.hpp	2011-05-19 09:16:32 +0000
@@ -309,4 +309,12 @@ GSN_PRINT_SIGNATURE(printDBINFO_SCAN_REF
 GSN_PRINT_SIGNATURE(printNODE_PING_REQ);
 GSN_PRINT_SIGNATURE(printNODE_PING_CONF);
 
+GSN_PRINT_SIGNATURE(printINDEX_STAT_REQ);
+GSN_PRINT_SIGNATURE(printINDEX_STAT_CONF);
+GSN_PRINT_SIGNATURE(printINDEX_STAT_REF);
+GSN_PRINT_SIGNATURE(printINDEX_STAT_IMPL_REQ);
+GSN_PRINT_SIGNATURE(printINDEX_STAT_IMPL_CONF);
+GSN_PRINT_SIGNATURE(printINDEX_STAT_IMPL_REF);
+GSN_PRINT_SIGNATURE(printINDEX_STAT_REP);
+
 #endif

=== modified file 'storage/ndb/include/kernel/signaldata/SumaImpl.hpp'
--- a/storage/ndb/include/kernel/signaldata/SumaImpl.hpp	2011-05-25 14:31:47 +0000
+++ b/storage/ndb/include/kernel/signaldata/SumaImpl.hpp	2011-05-29 10:55:32 +0000
@@ -250,7 +250,7 @@ struct SubSyncReq {
   friend class Suma;
 
   friend bool printSUB_SYNC_REQ(FILE *, const Uint32 *, Uint32, Uint16);
-  STATIC_CONST( SignalLength = 7 );
+  STATIC_CONST( SignalLength = 8 );
   
   Uint32 senderRef;
   Uint32 senderData;
@@ -259,16 +259,21 @@ struct SubSyncReq {
   Uint32 part; // SubscriptionData::Part
   Uint32 requestInfo;
   Uint32 fragCount;
+  Uint32 fragId; // ZNIL if not used
 
   enum {
     LM_Exclusive = 0x1
     ,Reorg = 0x2
     ,NoDisk = 0x4
     ,TupOrder = 0x8
+    ,LM_CommittedRead = 0x10
+    ,RangeScan = 0x20
+    ,StatScan = 0x40
   };
 
   SECTION( ATTRIBUTE_LIST = 0); // Used when doing SingelTableScan  
   SECTION( TABLE_LIST = 1 );
+  SECTION( TUX_BOUND_INFO = 1); // If range scan
 };
 
 struct SubSyncRef {

=== modified file 'storage/ndb/include/kernel/signaldata/TuxBound.hpp'
--- a/storage/ndb/include/kernel/signaldata/TuxBound.hpp	2011-02-01 23:27:25 +0000
+++ b/storage/ndb/include/kernel/signaldata/TuxBound.hpp	2011-05-19 09:40:36 +0000
@@ -31,7 +31,10 @@ public:
     BoundLT = 1,        // bit 0 for strict
     BoundGE = 2,
     BoundGT = 3,
-    BoundEQ = 4
+    BoundEQ = 4,
+    // stats scan parameter ids
+    StatSaveSize = 11,
+    StatSaveScale = 12
   };
   enum ErrorCode {
     InvalidAttrInfo = 4110,

=== modified file 'storage/ndb/include/kernel/signaldata/TuxContinueB.hpp'
--- a/storage/ndb/include/kernel/signaldata/TuxContinueB.hpp	2011-02-01 23:27:25 +0000
+++ b/storage/ndb/include/kernel/signaldata/TuxContinueB.hpp	2011-05-19 09:40:36 +0000
@@ -25,7 +25,8 @@ class TuxContinueB {
   friend class Dbtux;
 private:
   enum {
-    DropIndex =                 1
+    DropIndex = 1, // unused
+    StatMon = 2
   };
 };
 

=== modified file 'storage/ndb/include/kernel/signaldata/UtilRelease.hpp'
--- a/storage/ndb/include/kernel/signaldata/UtilRelease.hpp	2011-02-01 23:27:25 +0000
+++ b/storage/ndb/include/kernel/signaldata/UtilRelease.hpp	2011-05-19 09:38:03 +0000
@@ -17,7 +17,7 @@
 */
 
 #ifndef UTIL_RELEASE_HPP
-#define UTIL_PREPARE_HPP
+#define UTIL_RELEASE_HPP
 
 #include "SignalData.hpp"
 

=== modified file 'storage/ndb/include/mgmapi/mgmapi_config_parameters.h'
--- a/storage/ndb/include/mgmapi/mgmapi_config_parameters.h	2011-04-15 13:52:53 +0000
+++ b/storage/ndb/include/mgmapi/mgmapi_config_parameters.h	2011-05-31 08:28:58 +0000
@@ -186,6 +186,14 @@
 
 #define CFG_DB_START_NO_NODEGROUP_TIMEOUT 619
 
+#define CFG_DB_INDEX_STAT_AUTO_CREATE    620
+#define CFG_DB_INDEX_STAT_AUTO_UPDATE    621
+#define CFG_DB_INDEX_STAT_SAVE_SIZE      622
+#define CFG_DB_INDEX_STAT_SAVE_SCALE     623
+#define CFG_DB_INDEX_STAT_TRIGGER_PCT    624
+#define CFG_DB_INDEX_STAT_TRIGGER_SCALE  625
+#define CFG_DB_INDEX_STAT_UPDATE_DELAY   626
+
 #define CFG_NODE_ARBIT_RANK           200
 #define CFG_NODE_ARBIT_DELAY          201
 #define CFG_RESERVED_SEND_BUFFER_MEMORY 202

=== modified file 'storage/ndb/include/ndb_constants.h'
--- a/storage/ndb/include/ndb_constants.h	2011-02-16 14:53:53 +0000
+++ b/storage/ndb/include/ndb_constants.h	2011-05-31 08:28:58 +0000
@@ -112,4 +112,16 @@
  */
 #define NDB_BACKUP_SEQUENCE 0x1F000000
 
+/**
+ * Defines for index statistics
+ */
+#define NDB_INDEX_STAT_DB     "mysql"
+#define NDB_INDEX_STAT_SCHEMA "def"
+
+#define NDB_INDEX_STAT_HEAD_TABLE    "NDB$IS_HEAD"
+#define NDB_INDEX_STAT_SAMPLE_TABLE  "NDB$IS_SAMPLE"
+#define NDB_INDEX_STAT_SAMPLE_INDEX1 "NDB$IS_SAMPLE_X1"
+
+#define NDB_INDEX_STAT_PREFIX        "NDB$IS"
+
 #endif

=== modified file 'storage/ndb/src/common/debugger/signaldata/CMakeLists.txt'
--- a/storage/ndb/src/common/debugger/signaldata/CMakeLists.txt	2011-04-09 15:48:21 +0000
+++ b/storage/ndb/src/common/debugger/signaldata/CMakeLists.txt	2011-05-23 15:46:53 +0000
@@ -44,5 +44,6 @@ ADD_LIBRARY(ndbsignaldata STATIC
         LqhTrans.cpp ReadNodesConf.cpp CntrStart.cpp
         ScanFrag.cpp ApiVersion.cpp
         LocalRouteOrd.cpp
-	DbinfoScan.cpp NodePing.cpp)
+	DbinfoScan.cpp NodePing.cpp
+	IndexStatSignal.cpp)
 

=== added file 'storage/ndb/src/common/debugger/signaldata/IndexStatSignal.cpp'
--- a/storage/ndb/src/common/debugger/signaldata/IndexStatSignal.cpp	1970-01-01 00:00:00 +0000
+++ b/storage/ndb/src/common/debugger/signaldata/IndexStatSignal.cpp	2011-05-19 09:16:32 +0000
@@ -0,0 +1,152 @@
+/* Copyright (C) 2003 MySQL AB
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; version 2 of the License.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
+
+#include <signaldata/IndexStatSignal.hpp>
+
+static void
+get_req_rt_name(Uint32 rt, char* rt_name)
+{
+  strcpy(rt_name, "Unknown");
+#define set_req_rt_name(x) if (rt == IndexStatReq::x) strcpy(rt_name, #x)
+  set_req_rt_name(RT_UPDATE_STAT);
+  set_req_rt_name(RT_CLEAN_NEW);
+  set_req_rt_name(RT_SCAN_FRAG);
+  set_req_rt_name(RT_CLEAN_OLD);
+  set_req_rt_name(RT_START_MON);
+  set_req_rt_name(RT_DELETE_STAT);
+  set_req_rt_name(RT_STOP_MON);
+  set_req_rt_name(RT_DROP_HEAD);
+  set_req_rt_name(RT_CLEAN_ALL);
+#undef set_req_rt_name
+}
+
+static void
+get_rep_rt_name(Uint32 rt, char* rt_name)
+{
+  strcpy(rt_name, "Unknown");
+#define set_rep_rt_name(x) if (rt == IndexStatRep::x) strcpy(rt_name, #x)
+  set_rep_rt_name(RT_UPDATE_REQ);
+  set_rep_rt_name(RT_UPDATE_CONF);
+#undef set_rep_rt_name
+}
+
+bool
+printINDEX_STAT_REQ(FILE* output, const Uint32* theData, Uint32 len, Uint16)
+{
+  const IndexStatReq* sig = (const IndexStatReq*)theData;
+  fprintf(output, " clientRef: 0x%x", sig->clientRef);
+  fprintf(output, " clientData: %u", sig->clientData);
+  fprintf(output, "\n");
+  Uint32 rt = sig->requestInfo & 0xFF;
+  char rt_name[40];
+  get_req_rt_name(rt, rt_name);
+  fprintf(output, " requestType: %s[%u]", rt_name, rt);
+  fprintf(output, " requestFlag: 0x%x", sig->requestFlag);
+  fprintf(output, "\n");
+  fprintf(output, " indexId: %u", sig->indexId);
+  fprintf(output, " indexVersion: %u", sig->indexVersion);
+  fprintf(output, " tableId: %u", sig->tableId);
+  fprintf(output, "\n");
+  return true;
+}
+
+bool
+printINDEX_STAT_IMPL_REQ(FILE* output, const Uint32* theData, Uint32 len, Uint16)
+{
+  const IndexStatImplReq* sig = (const IndexStatImplReq*)theData;
+  fprintf(output, " senderRef: 0x%x", sig->senderRef);
+  fprintf(output, " senderData: %u", sig->senderData);
+  fprintf(output, "\n");
+  Uint32 rt = sig->requestType;
+  char rt_name[40];
+  get_req_rt_name(rt, rt_name);
+  fprintf(output, " requestType: %s[%u]", rt_name, rt);
+  fprintf(output, " requestFlag: 0x%x", sig->requestFlag);
+  fprintf(output, "\n");
+  fprintf(output, " indexId: %u", sig->indexId);
+  fprintf(output, " indexVersion: %u", sig->indexVersion);
+  fprintf(output, " tableId: %u", sig->tableId);
+  fprintf(output, " fragId: %u", sig->fragId);
+  fprintf(output, " fragCount: %u", sig->fragCount);
+  fprintf(output, "\n");
+  return true;
+}
+
+bool
+printINDEX_STAT_CONF(FILE* output, const Uint32* theData, Uint32 len, Uint16)
+{
+  const IndexStatConf* sig = (const IndexStatConf*)theData;
+  fprintf(output, " senderRef: 0x%x", sig->senderRef);
+  fprintf(output, " senderData: %u", sig->senderData);
+  fprintf(output, "\n");
+  return true;
+}
+
+bool
+printINDEX_STAT_IMPL_CONF(FILE* output, const Uint32* theData, Uint32 len, Uint16)
+{
+  const IndexStatImplConf* sig = (const IndexStatImplConf*)theData;
+  fprintf(output, " senderRef: 0x%x", sig->senderRef);
+  fprintf(output, " senderData: %u", sig->senderData);
+  fprintf(output, "\n");
+  return true;
+}
+
+bool
+printINDEX_STAT_REF(FILE* output, const Uint32* theData, Uint32 len, Uint16)
+{
+  const IndexStatRef* sig = (const IndexStatRef*)theData;
+  fprintf(output, " senderRef: 0x%x", sig->senderRef);
+  fprintf(output, " senderData: %u", sig->senderData);
+  fprintf(output, " errorCode: %u", sig->errorCode);
+  fprintf(output, " errorLine: %u", sig->errorLine);
+  fprintf(output, "\n");
+  return true;
+}
+
+bool
+printINDEX_STAT_IMPL_REF(FILE* output, const Uint32* theData, Uint32 len, Uint16)
+{
+  const IndexStatImplRef* sig = (const IndexStatImplRef*)theData;
+  fprintf(output, " senderRef: 0x%x", sig->senderRef);
+  fprintf(output, " senderData: %u", sig->senderData);
+  fprintf(output, " errorCode: %u", sig->errorCode);
+  fprintf(output, " errorLine: %u", sig->errorLine);
+  fprintf(output, "\n");
+  return true;
+}
+
+bool
+printINDEX_STAT_REP(FILE* output, const Uint32* theData, Uint32 len, Uint16)
+{
+  const IndexStatRep* sig = (const IndexStatRep*)theData;
+  fprintf(output, " senderRef: 0x%x", sig->senderRef);
+  fprintf(output, " senderData: %u", sig->senderData);
+  fprintf(output, "\n");
+  Uint32 rt = sig->requestType;
+  char rt_name[40];
+  get_rep_rt_name(rt, rt_name);
+  fprintf(output, " requestType: %s[%u]", rt_name, rt);
+  fprintf(output, " requestFlag: 0x%x", sig->requestFlag);
+  fprintf(output, "\n");
+  fprintf(output, " indexId: %u", sig->indexId);
+  fprintf(output, " indexVersion: %u", sig->indexVersion);
+  fprintf(output, " tableId: %u", sig->tableId);
+  fprintf(output, "\n");
+  fprintf(output, " fragId: %u", sig->fragId);
+  fprintf(output, " loadTime: %u", sig->loadTime);
+  fprintf(output, "\n");
+  return true;
+}

=== modified file 'storage/ndb/src/common/debugger/signaldata/Makefile.am'
--- a/storage/ndb/src/common/debugger/signaldata/Makefile.am	2011-04-09 15:48:21 +0000
+++ b/storage/ndb/src/common/debugger/signaldata/Makefile.am	2011-05-19 09:16:32 +0000
@@ -46,7 +46,8 @@ libsignaldataprint_la_SOURCES = \
   	  CreateTrigImpl.cpp DropTrigImpl.cpp \
  	  CreateIndxImpl.cpp DropIndxImpl.cpp AlterIndxImpl.cpp \
  	  BuildIndx.cpp BuildIndxImpl.cpp ApiVersion.cpp \
-          LocalRouteOrd.cpp DbinfoScan.cpp NodePing.cpp
+          LocalRouteOrd.cpp DbinfoScan.cpp NodePing.cpp \
+	  IndexStatSignal.cpp
 
 include $(top_srcdir)/storage/ndb/config/common.mk.am
 include $(top_srcdir)/storage/ndb/config/type_ndbapi.mk.am

=== modified file 'storage/ndb/src/common/debugger/signaldata/ScanFrag.cpp'
--- a/storage/ndb/src/common/debugger/signaldata/ScanFrag.cpp	2011-02-08 13:55:54 +0000
+++ b/storage/ndb/src/common/debugger/signaldata/ScanFrag.cpp	2011-05-19 09:16:32 +0000
@@ -51,8 +51,11 @@ printSCAN_FRAGREQ(FILE * output, const U
           ScanFragReq::getAttrLen(sig->requestInfo));
   fprintf(output, " reorg: %u",
           ScanFragReq::getReorgFlag(sig->requestInfo));
-  fprintf(output, " corr: %u\n",
+  fprintf(output, " corr: %u",
           ScanFragReq::getCorrFactorFlag(sig->requestInfo));
+  fprintf(output, " stat: %u",
+          ScanFragReq::getStatScanFlag(sig->requestInfo));
+  fprintf(output, "\n");
 
   fprintf(output, " tableId: %u\n", sig->tableId);
   fprintf(output, " fragmentNo: %u\n", sig->fragmentNoKeyLen & 0xFFFF);

=== modified file 'storage/ndb/src/common/debugger/signaldata/SchemaTransImpl.cpp'
--- a/storage/ndb/src/common/debugger/signaldata/SchemaTransImpl.cpp	2011-02-02 00:40:07 +0000
+++ b/storage/ndb/src/common/debugger/signaldata/SchemaTransImpl.cpp	2011-05-19 09:16:32 +0000
@@ -137,6 +137,9 @@ printSCHEMA_TRANS_IMPL_REQ(FILE* output,
     case GSN_BUILD_INDX_IMPL_REQ:
       printBUILD_INDX_IMPL_REQ(output, pb_data, pb_len, rbn);
       break;
+    case GSN_INDEX_STAT_IMPL_REQ:
+      printINDEX_STAT_IMPL_REQ(output, pb_data, pb_len, rbn);
+      break;
     default:
     {
       Uint32 i;

=== modified file 'storage/ndb/src/common/debugger/signaldata/SignalDataPrint.cpp'
--- a/storage/ndb/src/common/debugger/signaldata/SignalDataPrint.cpp	2011-04-09 15:48:21 +0000
+++ b/storage/ndb/src/common/debugger/signaldata/SignalDataPrint.cpp	2011-05-19 09:16:32 +0000
@@ -260,6 +260,14 @@ SignalDataPrintFunctions[] = {
   ,{ GSN_NODE_PING_REQ, printNODE_PING_REQ }
   ,{ GSN_NODE_PING_CONF, printNODE_PING_CONF }
 
+  ,{ GSN_INDEX_STAT_REQ, printINDEX_STAT_REQ }
+  ,{ GSN_INDEX_STAT_CONF, printINDEX_STAT_CONF }
+  ,{ GSN_INDEX_STAT_REF, printINDEX_STAT_REF }
+  ,{ GSN_INDEX_STAT_IMPL_REQ, printINDEX_STAT_IMPL_REQ }
+  ,{ GSN_INDEX_STAT_IMPL_CONF, printINDEX_STAT_IMPL_CONF }
+  ,{ GSN_INDEX_STAT_IMPL_REF, printINDEX_STAT_IMPL_REF }
+  ,{ GSN_INDEX_STAT_REP, printINDEX_STAT_REP }
+
   ,{ 0, 0 }
 };
 

=== modified file 'storage/ndb/src/common/debugger/signaldata/SignalNames.cpp'
--- a/storage/ndb/src/common/debugger/signaldata/SignalNames.cpp	2011-04-09 15:48:21 +0000
+++ b/storage/ndb/src/common/debugger/signaldata/SignalNames.cpp	2011-05-19 09:16:32 +0000
@@ -763,5 +763,13 @@ const GsnName SignalNames [] = {
 
   ,{ GSN_NODE_PING_REQ, "NODE_PING_REQ" }
   ,{ GSN_NODE_PING_CONF, "NODE_PING_CONF" }
+
+  ,{ GSN_INDEX_STAT_REQ, "INDEX_STAT_REQ" }
+  ,{ GSN_INDEX_STAT_CONF, "INDEX_STAT_CONF" }
+  ,{ GSN_INDEX_STAT_REF, "INDEX_STAT_REF" }
+  ,{ GSN_INDEX_STAT_IMPL_REQ, "INDEX_STAT_IMPL_REQ" }
+  ,{ GSN_INDEX_STAT_IMPL_CONF, "INDEX_STAT_IMPL_CONF" }
+  ,{ GSN_INDEX_STAT_IMPL_REF, "INDEX_STAT_IMPL_REF" }
+  ,{ GSN_INDEX_STAT_REP, "INDEX_STAT_REP" }
 };
 const unsigned short NO_OF_SIGNAL_NAMES = sizeof(SignalNames)/sizeof(GsnName);

=== modified file 'storage/ndb/src/kernel/blocks/ERROR_codes.txt'
--- a/storage/ndb/src/kernel/blocks/ERROR_codes.txt	2011-05-19 07:32:39 +0000
+++ b/storage/ndb/src/kernel/blocks/ERROR_codes.txt	2011-05-25 15:03:11 +0000
@@ -30,6 +30,7 @@ Next SUMA 13047
 Next LGMAN 15001
 Next TSMAN 16001
 Next DBSPJ 17000
+Next TRIX 18000
 
 TESTING NODE FAILURE, ARBITRATION
 ---------------------------------
@@ -721,3 +722,15 @@ ACC bug#34348
 3002: limit frags to 2 directory pages (instead of 256)
       print (if VM_TRACE) LH variables at expand and shrink
       apply before mass data load, reset with error insert 0
+
+TRIX
+----
+Index stats:
+18001: fail to seize statOp
+18002: fail to seize associated subRec
+18011: fail HEAD read - simulate UtilExecuteRef::AllocationError
+18012: fail HEAD insert/update/delete - as in 18011
+18021: fail RT_CLEAN_NEW - simulate TC error 626
+18022: fail RT_CLEAN_OLD (non-fatal) - as in 18021
+18023: fail RT_CLEAN_ALL (non-fatal) - as in 18021
+18024: fail RT_SCAN_FRAG - simulate TC error 630

=== modified file 'storage/ndb/src/kernel/blocks/cmvmi/Cmvmi.cpp'
--- a/storage/ndb/src/kernel/blocks/cmvmi/Cmvmi.cpp	2011-05-19 07:32:39 +0000
+++ b/storage/ndb/src/kernel/blocks/cmvmi/Cmvmi.cpp	2011-05-25 15:03:11 +0000
@@ -1436,6 +1436,11 @@ void Cmvmi::execTAMPER_ORD(Signal* signa
     jam();
     tuserblockref = DBSPJ_REF;
   }
+  else if (errNo < 19000)
+  {
+    jam();
+    tuserblockref = TRIX_REF;
+  }
   else if (errNo < 30000)
   {
     /*--------------------------------------------------------------------*/

=== modified file 'storage/ndb/src/kernel/blocks/dbdict/Dbdict.cpp'
--- a/storage/ndb/src/kernel/blocks/dbdict/Dbdict.cpp	2011-05-17 23:29:55 +0000
+++ b/storage/ndb/src/kernel/blocks/dbdict/Dbdict.cpp	2011-05-31 08:28:58 +0000
@@ -101,6 +101,8 @@ extern EventLogger * g_eventLogger;
 #include <signaldata/DbinfoScan.hpp>
 #include <signaldata/TransIdAI.hpp>
 
+#include <signaldata/IndexStatSignal.hpp>
+
 #define ZNOT_FOUND 626
 #define ZALREADYEXIST 630
 
@@ -380,6 +382,12 @@ void Dbdict::execDBINFO_SCANREQ(Signal *
         c_buildIndexRecPool.getEntrySize(),
         c_buildIndexRecPool.getUsedHi(),
         { 0,0,0,0 }},
+      { "Index Stat Record",
+        c_indexStatRecPool.getUsed(),
+        c_indexStatRecPool.getSize(),
+        c_indexStatRecPool.getEntrySize(),
+        c_indexStatRecPool.getUsedHi(),
+        { 0,0,0,0 }},
       { "Create Hash Map Record",
         c_createHashMapRecPool.getUsed(),
         c_createHashMapRecPool.getSize(),
@@ -505,6 +513,10 @@ void Dbdict::execCONTINUEB(Signal* signa
     execDICT_TAKEOVER_REQ(signal);
   }
   break;
+  case ZINDEX_STAT_BG_PROCESS:
+    jam();
+    indexStatBg_process(signal);
+    break;
 #ifdef ERROR_INSERT
   case 6103: // search for it
     jam();
@@ -2257,6 +2269,14 @@ Dbdict::Dbdict(Block_context& ctx):
   addRecSignal(GSN_DROP_NODEGROUP_REQ, &Dbdict::execDROP_NODEGROUP_REQ);
   addRecSignal(GSN_DROP_NODEGROUP_IMPL_REF, &Dbdict::execDROP_NODEGROUP_IMPL_REF);
   addRecSignal(GSN_DROP_NODEGROUP_IMPL_CONF, &Dbdict::execDROP_NODEGROUP_IMPL_CONF);
+
+  // ordered index statistics
+  addRecSignal(GSN_INDEX_STAT_REQ, &Dbdict::execINDEX_STAT_REQ);
+  addRecSignal(GSN_INDEX_STAT_CONF, &Dbdict::execINDEX_STAT_CONF);
+  addRecSignal(GSN_INDEX_STAT_REF, &Dbdict::execINDEX_STAT_REF);
+  addRecSignal(GSN_INDEX_STAT_IMPL_CONF, &Dbdict::execINDEX_STAT_IMPL_CONF);
+  addRecSignal(GSN_INDEX_STAT_IMPL_REF, &Dbdict::execINDEX_STAT_IMPL_REF);
+  addRecSignal(GSN_INDEX_STAT_REP, &Dbdict::execINDEX_STAT_REP);
 }//Dbdict::Dbdict()
 
 Dbdict::~Dbdict() 
@@ -2457,6 +2477,9 @@ void Dbdict::initialiseTableRecord(Table
   tablePtr.p->buildTriggerId = RNIL;
   tablePtr.p->m_read_locked= 0;
   tablePtr.p->storageType = NDB_STORAGETYPE_DEFAULT;
+  tablePtr.p->indexStatFragId = ZNIL;
+  tablePtr.p->indexStatNodeId = ZNIL;
+  tablePtr.p->indexStatBgRequest = 0;
 }//Dbdict::initialiseTableRecord()
 
 void Dbdict::initTriggerRecords()
@@ -2680,6 +2703,14 @@ void Dbdict::execSTTOR(Signal* signal)
                c_restartType == NodeState::ST_INITIAL_NODE_RESTART ||
                c_restartType == NodeState::ST_NODE_RESTART);
     break;
+  case 7:
+    /*
+     * config cannot yet be changed dynamically but we start the
+     * loop always anyway because the cost is minimal
+     */
+    c_indexStatBgId = 0;
+    indexStatBg_sendContinueB(signal);
+    break;
   }
   sendSTTORRY(signal);
 }//execSTTOR()
@@ -2691,8 +2722,9 @@ void Dbdict::sendSTTORRY(Signal* signal)
   signal->theData[2] = 0;       /* garbage */
   signal->theData[3] = 1;       /* first wanted start phase */
   signal->theData[4] = 3;       /* get type of start */
-  signal->theData[5] = ZNOMOREPHASES;
-  sendSignal(NDBCNTR_REF, GSN_STTORRY, signal, 6, JBB);
+  signal->theData[5] = 7;       /* start index stat bg loop */
+  signal->theData[6] = ZNOMOREPHASES;
+  sendSignal(NDBCNTR_REF, GSN_STTORRY, signal, 7, JBB);
 }
 
 /* ---------------------------------------------------------------- */
@@ -2716,6 +2748,10 @@ void Dbdict::execREAD_CONFIG_REQ(Signal*
 					&c_maxNoOfTriggers));
   ndbrequire(!ndb_mgm_get_int_parameter(p, CFG_DICT_ATTRIBUTE,&attributesize));
   ndbrequire(!ndb_mgm_get_int_parameter(p, CFG_DICT_TABLE, &tablerecSize));
+  ndbrequire(!ndb_mgm_get_int_parameter(p, CFG_DB_INDEX_STAT_AUTO_CREATE,
+                                        &c_indexStatAutoCreate));
+  ndbrequire(!ndb_mgm_get_int_parameter(p, CFG_DB_INDEX_STAT_AUTO_UPDATE,
+                                        &c_indexStatAutoUpdate));
 
   c_attributeRecordPool.setSize(attributesize);
   c_attributeRecordHash.setSize(64);
@@ -2763,6 +2799,7 @@ void Dbdict::execREAD_CONFIG_REQ(Signal*
   c_dropIndexRecPool.setSize(2 * MAX_INDEXES);
   c_alterIndexRecPool.setSize(2 * MAX_INDEXES);
   c_buildIndexRecPool.setSize(2 * 2 * MAX_INDEXES);
+  c_indexStatRecPool.setSize((1 + 4) * MAX_INDEXES); //main + 4 subs
   c_createFilegroupRecPool.setSize(32);
   c_createFileRecPool.setSize(32);
   c_dropFilegroupRecPool.setSize(32);
@@ -11192,6 +11229,7 @@ Dbdict::createIndex_fromCreateTable(Sign
     ndbrequire(conf->transId == trans_ptr.p->m_transId);
     impl_req->indexVersion = conf->tableVersion;
     createIndexPtr.p->m_sub_create_table = true;
+
     createSubOps(signal, op_ptr);
   } else {
     jam();
@@ -11984,6 +12022,15 @@ Dbdict::alterIndex_parse(Signal* signal,
   }
   c_tableRecordPool.getPtr(indexPtr, impl_req->indexId);
 
+  // get name for system index check later
+  char indexName[MAX_TAB_NAME_SIZE];
+  memset(indexName, 0, sizeof(indexName));
+  {
+    ConstRope r(c_rope_pool, indexPtr.p->tableName);
+    r.copy(indexName);
+  }
+  D("index " << indexName);
+
   if (indexPtr.p->tableVersion != impl_req->indexVersion) {
     jam();
     setError(error, AlterIndxRef::InvalidIndexVersion, __LINE__);
@@ -12101,6 +12148,46 @@ Dbdict::alterIndex_parse(Signal* signal,
   // set attribute mask (of primary table attribute ids)
   getIndexAttrMask(indexPtr, alterIndexPtr.p->m_attrMask);
 
+  // ordered index stats
+  if (indexPtr.p->tableType == DictTabInfo::OrderedIndex) {
+    jam();
+
+    // always compute monitored replica
+    if (requestType == AlterIndxImplReq::AlterIndexOnline) {
+      jam();
+      set_index_stat_frag(signal, indexPtr);
+    }
+
+    // skip system indexes (at least index stats indexes)
+    if (strstr(indexName, "/" NDB_INDEX_STAT_PREFIX) != 0) {
+      jam();
+      D("skip index stats operations for system index");
+      alterIndexPtr.p->m_sub_index_stat_dml = true;
+      alterIndexPtr.p->m_sub_index_stat_mon = true;
+    }
+
+    // disable update/delete if db not up
+    if (getNodeState().startLevel != NodeState::SL_STARTED &&
+        getNodeState().startLevel != NodeState::SL_SINGLEUSER) {
+      jam();
+      alterIndexPtr.p->m_sub_index_stat_dml = true;
+    }
+
+    // disable update on create if not auto
+    if (requestType == AlterIndxImplReq::AlterIndexOnline &&
+        !c_indexStatAutoCreate) {
+      jam();
+      alterIndexPtr.p->m_sub_index_stat_dml = true;
+    }
+
+    // always delete on drop because manual update may have been done
+    if (requestType == AlterIndxImplReq::AlterIndexOffline) {
+      jam();
+    }
+
+    // always assign/remove monitored replica in TUX instances
+  }
+
   if (ERROR_INSERTED(6123)) {
     jam();
     CLEAR_ERROR_INSERT_VALUE;
@@ -12109,6 +12196,32 @@ Dbdict::alterIndex_parse(Signal* signal,
   }
 }
 
+void
+Dbdict::set_index_stat_frag(Signal* signal, TableRecordPtr indexPtr)
+{
+  jam();
+  const Uint32 indexId = indexPtr.i;
+  Uint32 err = get_fragmentation(signal, indexId);
+  ndbrequire(err == 0);
+  // format: R F { fragId node1 .. nodeR } x { F }
+  // fragId: 0 1 2 .. (or whatever)
+  const Uint16* frag_data = (Uint16*)(signal->theData+25);
+  const Uint32 noOfFragments = frag_data[1];
+  const Uint32 noOfReplicas = frag_data[0];
+  ndbrequire(noOfFragments != 0 && noOfReplicas != 0);
+
+  // distribute by table and index id
+  const Uint32 value = indexPtr.p->primaryTableId + indexPtr.i;
+  const Uint32 fragId = value % noOfFragments;
+  const Uint32 fragIndex = 2 + (1 + noOfReplicas) * fragId;
+  const Uint32 nodeIndex = value % noOfReplicas;
+  const Uint32 nodeId = frag_data[fragIndex + 1 + nodeIndex];
+
+  D("set_index_stat_frag" << V(indexId) << V(fragId) << V(nodeId));
+  indexPtr.p->indexStatFragId = fragId;
+  indexPtr.p->indexStatNodeId = nodeId;
+}
+
 bool
 Dbdict::alterIndex_subOps(Signal* signal, SchemaOpPtr op_ptr)
 {
@@ -12118,6 +12231,8 @@ Dbdict::alterIndex_subOps(Signal* signal
   getOpRec(op_ptr, alterIndexPtr);
   const AlterIndxImplReq* impl_req = &alterIndexPtr.p->m_request;
   Uint32 requestType = impl_req->requestType;
+  TableRecordPtr indexPtr;
+  c_tableRecordPool.getPtr(indexPtr, impl_req->indexId);
 
   // ops to create or drop triggers
   if (alterIndexPtr.p->m_sub_trigger == false)
@@ -12169,6 +12284,19 @@ Dbdict::alterIndex_subOps(Signal* signal
     return true;
   }
 
+  if (indexPtr.p->isOrderedIndex() &&
+      (!alterIndexPtr.p->m_sub_index_stat_dml ||
+       !alterIndexPtr.p->m_sub_index_stat_mon)) {
+    jam();
+    Callback c = {
+      safe_cast(&Dbdict::alterIndex_fromIndexStat),
+      op_ptr.p->op_key
+    };
+    op_ptr.p->m_callback = c;
+    alterIndex_toIndexStat(signal, op_ptr);
+    return true;
+  }
+
   return false;
 }
 
@@ -12404,6 +12532,93 @@ Dbdict::alterIndex_fromBuildIndex(Signal
 }
 
 void
+Dbdict::alterIndex_toIndexStat(Signal* signal, SchemaOpPtr op_ptr)
+{
+  D("alterIndex_toIndexStat");
+
+  SchemaTransPtr trans_ptr = op_ptr.p->m_trans_ptr;
+  AlterIndexRecPtr alterIndexPtr;
+  getOpRec(op_ptr, alterIndexPtr);
+  const AlterIndxImplReq* impl_req = &alterIndexPtr.p->m_request;
+
+  IndexStatReq* req = (IndexStatReq*)signal->getDataPtrSend();
+
+  Uint32 requestType = 0;
+  switch (impl_req->requestType) {
+  case AlterIndxImplReq::AlterIndexOnline:
+    if (!alterIndexPtr.p->m_sub_index_stat_dml)
+      requestType = IndexStatReq::RT_UPDATE_STAT;
+    else
+      requestType = IndexStatReq::RT_START_MON;
+    break;
+  case AlterIndxImplReq::AlterIndexOffline:
+    if (!alterIndexPtr.p->m_sub_index_stat_dml)
+      requestType = IndexStatReq::RT_DELETE_STAT;
+    else
+      requestType = IndexStatReq::RT_STOP_MON;
+    break;
+  default:
+    ndbrequire(false);
+    break;
+  }
+
+  Uint32 requestInfo = 0;
+  DictSignal::setRequestType(requestInfo, requestType);
+  DictSignal::addRequestFlagsGlobal(requestInfo, op_ptr.p->m_requestInfo);
+
+  TableRecordPtr indexPtr;
+  c_tableRecordPool.getPtr(indexPtr, impl_req->indexId);
+
+  req->clientRef = reference();
+  req->clientData = op_ptr.p->op_key;
+  req->transId = trans_ptr.p->m_transId;
+  req->transKey = trans_ptr.p->trans_key;
+  req->requestInfo = requestInfo;
+  req->requestFlag = 0;
+  req->indexId = impl_req->indexId;
+  req->indexVersion = indexPtr.p->tableVersion;
+  req->tableId = impl_req->tableId;
+
+  sendSignal(reference(), GSN_INDEX_STAT_REQ,
+             signal, IndexStatReq::SignalLength, JBB);
+}
+
+void
+Dbdict::alterIndex_fromIndexStat(Signal* signal, Uint32 op_key, Uint32 ret)
+{
+  jam();
+  D("alterIndex_fromIndexStat");
+
+  SchemaOpPtr op_ptr;
+  AlterIndexRecPtr alterIndexPtr;
+
+  findSchemaOp(op_ptr, alterIndexPtr, op_key);
+  ndbrequire(!op_ptr.isNull());
+  SchemaTransPtr trans_ptr = op_ptr.p->m_trans_ptr;
+
+  if (ret == 0) {
+    jam();
+    const IndexStatConf* conf =
+      (const IndexStatConf*)signal->getDataPtr();
+
+    ndbrequire(conf->transId == trans_ptr.p->m_transId);
+    ndbrequire(!alterIndexPtr.p->m_sub_index_stat_dml ||
+               !alterIndexPtr.p->m_sub_index_stat_mon);
+    alterIndexPtr.p->m_sub_index_stat_dml = true;
+    alterIndexPtr.p->m_sub_index_stat_mon = true;
+    createSubOps(signal, op_ptr);
+  } else {
+    jam();
+    const IndexStatRef* ref =
+      (const IndexStatRef*)signal->getDataPtr();
+
+    ErrorInfo error;
+    setError(error, ref);
+    abortSubOps(signal, op_ptr, error);
+  }
+}
+
+void
 Dbdict::alterIndex_reply(Signal* signal, SchemaOpPtr op_ptr, ErrorInfo error)
 {
   D("alterIndex_reply" << V(op_ptr.i) << *op_ptr.p);
@@ -13645,6 +13860,755 @@ Dbdict::execBUILD_INDX_IMPL_REF(Signal*
 
 // BuildIndex: END
 
+// MODULE: IndexStat
+
+const Dbdict::OpInfo
+Dbdict::IndexStatRec::g_opInfo = {
+  { 'S', 'I', 'n', 0 },
+  GSN_INDEX_STAT_IMPL_REQ,
+  IndexStatImplReq::SignalLength,
+  //
+  &Dbdict::indexStat_seize,
+  &Dbdict::indexStat_release,
+  //
+  &Dbdict::indexStat_parse,
+  &Dbdict::indexStat_subOps,
+  &Dbdict::indexStat_reply,
+  //
+  &Dbdict::indexStat_prepare,
+  &Dbdict::indexStat_commit,
+  &Dbdict::indexStat_complete,
+  //
+  &Dbdict::indexStat_abortParse,
+  &Dbdict::indexStat_abortPrepare
+};
+
+bool
+Dbdict::indexStat_seize(SchemaOpPtr op_ptr)
+{
+  return seizeOpRec<IndexStatRec>(op_ptr);
+}
+
+void
+Dbdict::indexStat_release(SchemaOpPtr op_ptr)
+{
+  releaseOpRec<IndexStatRec>(op_ptr);
+}
+
+void
+Dbdict::execINDEX_STAT_REQ(Signal* signal)
+{
+  jamEntry();
+  if (!assembleFragments(signal)) {
+    jam();
+    return;
+  }
+  SectionHandle handle(this, signal);
+
+  const IndexStatReq req_copy =
+    *(const IndexStatReq*)signal->getDataPtr();
+  const IndexStatReq* req = &req_copy;
+
+  ErrorInfo error;
+  do {
+    SchemaOpPtr op_ptr;
+    IndexStatRecPtr indexStatPtr;
+    IndexStatImplReq* impl_req;
+
+    startClientReq(op_ptr, indexStatPtr, req, impl_req, error);
+    if (hasError(error)) {
+      jam();
+      break;
+    }
+
+    // senderRef, senderData, requestType have been set already
+    impl_req->transId = req->transId;
+    impl_req->requestFlag = req->requestFlag;
+    impl_req->indexId = req->indexId;
+    impl_req->indexVersion = req->indexVersion;
+    impl_req->tableId = req->tableId;
+    impl_req->fragId = ZNIL;
+    impl_req->fragCount = ZNIL;
+
+    handleClientReq(signal, op_ptr, handle);
+    return;
+  } while (0);
+
+  releaseSections(handle);
+
+  IndexStatRef* ref = (IndexStatRef*)signal->getDataPtrSend();
+  ref->senderRef = reference();
+  ref->clientData = req->clientData;
+  ref->transId = req->transId;
+  getError(error, ref);
+
+  sendSignal(req->clientRef, GSN_INDEX_STAT_REF, signal,
+             IndexStatRef::SignalLength, JBB);
+}
+
+// IndexStat: PARSE
+
+void
+Dbdict::indexStat_parse(Signal* signal, bool master,
+                        SchemaOpPtr op_ptr,
+                        SectionHandle& handle, ErrorInfo& error)
+{
+  D("indexStat_parse");
+
+  IndexStatRecPtr indexStatPtr;
+  getOpRec(op_ptr, indexStatPtr);
+  IndexStatImplReq* impl_req = &indexStatPtr.p->m_request;
+
+  // get index
+  TableRecordPtr indexPtr;
+  if (!(impl_req->indexId < c_tableRecordPool.getSize())) {
+    jam();
+    setError(error, IndexStatRef::InvalidIndex, __LINE__);
+    return;
+  }
+  c_tableRecordPool.getPtr(indexPtr, impl_req->indexId);
+
+  if (!indexPtr.p->isOrderedIndex()) {
+    jam();
+    setError(error, IndexStatRef::InvalidIndex, __LINE__);
+    return;
+  }
+
+  XSchemaFile* xsf = &c_schemaFile[SchemaRecord::NEW_SCHEMA_FILE];
+  const SchemaFile::TableEntry* te = getTableEntry(xsf, impl_req->indexId);
+  if (te->m_tableState != SchemaFile::SF_CREATE &&
+      te->m_tableState != SchemaFile::SF_IN_USE) {
+    jam();
+    setError(error, IndexStatRef::InvalidIndex, __LINE__);
+    return;
+  }
+
+  // fragmentId is defined only in signals from DICT to TRIX,TUX
+  if (impl_req->fragId != ZNIL) {
+    jam();
+    setError(error, IndexStatRef::InvalidRequest, __LINE__);
+    return;
+  }
+  impl_req->fragCount = indexPtr.p->fragmentCount;
+
+  switch (impl_req->requestType) {
+  case IndexStatReq::RT_UPDATE_STAT:
+    jam();
+    // clean new samples, scan, clean old samples, start frag monitor
+    indexStatPtr.p->m_subOpCount = 4;
+    indexStatPtr.p->m_subOpIndex = 0;
+    break;
+  case IndexStatReq::RT_DELETE_STAT:
+    jam();
+    // stop frag monitor, delete head, clean all samples
+    indexStatPtr.p->m_subOpCount = 3;
+    indexStatPtr.p->m_subOpIndex = 0;
+    break;
+  case IndexStatReq::RT_SCAN_FRAG:
+  case IndexStatReq::RT_CLEAN_NEW:
+  case IndexStatReq::RT_CLEAN_OLD:
+  case IndexStatReq::RT_CLEAN_ALL:
+  case IndexStatReq::RT_DROP_HEAD:
+  case IndexStatReq::RT_START_MON:
+  case IndexStatReq::RT_STOP_MON:
+    jam();
+    // sub-operations can be invoked only by DICT
+    if (master && refToBlock(op_ptr.p->m_clientRef) != DBDICT) {
+      jam();
+      setError(error, IndexStatRef::InvalidRequest, __LINE__);
+      return;
+    }
+    indexStatPtr.p->m_subOpCount = 0;
+    indexStatPtr.p->m_subOpIndex = 0;
+    break;
+  default:
+    jam();
+    setError(error, IndexStatRef::InvalidRequest, __LINE__);
+    return;
+  }
+}
+
+bool
+Dbdict::indexStat_subOps(Signal* signal, SchemaOpPtr op_ptr)
+{
+  D("indexStat_subOps" << V(op_ptr.i) << V(*op_ptr.p));
+
+  IndexStatRecPtr indexStatPtr;
+  getOpRec(op_ptr, indexStatPtr);
+  const IndexStatImplReq* impl_req = &indexStatPtr.p->m_request;
+
+  const Uint32 subOpIndex = indexStatPtr.p->m_subOpIndex;
+  const Uint32 subOpCount = indexStatPtr.p->m_subOpCount;
+  if (subOpIndex >= subOpCount) {
+    jam();
+    ndbrequire(subOpIndex == subOpCount);
+    return false;
+  }
+
+  Uint32 requestType = 0;
+
+  switch (impl_req->requestType) {
+  case IndexStatReq::RT_UPDATE_STAT:
+    if (subOpIndex == 0)
+      requestType = IndexStatReq::RT_CLEAN_NEW;
+    else if (subOpIndex == 1)
+      requestType = IndexStatReq::RT_SCAN_FRAG;
+    else if (subOpIndex == 2)
+      requestType = IndexStatReq::RT_CLEAN_OLD;
+    else if (subOpIndex == 3)
+      requestType = IndexStatReq::RT_START_MON;
+    break;
+
+  case IndexStatReq::RT_DELETE_STAT:
+    jam();
+    if (subOpIndex == 0)
+      requestType = IndexStatReq::RT_STOP_MON;
+    else if (subOpIndex == 1)
+      requestType = IndexStatReq::RT_DROP_HEAD;
+    else if (subOpIndex == 2)
+      requestType = IndexStatReq::RT_CLEAN_ALL;
+    break;
+  };
+
+  ndbrequire(requestType != 0);
+  Callback c = {
+    safe_cast(&Dbdict::indexStat_fromIndexStat),
+    op_ptr.p->op_key
+  };
+  op_ptr.p->m_callback = c;
+  indexStat_toIndexStat(signal, op_ptr, requestType);
+  return true;
+}
+
+void
+Dbdict::indexStat_toIndexStat(Signal* signal, SchemaOpPtr op_ptr,
+                              Uint32 requestType)
+{
+  D("indexStat_toIndexStat");
+
+  SchemaTransPtr trans_ptr = op_ptr.p->m_trans_ptr;
+  IndexStatRecPtr indexStatPtr;
+  getOpRec(op_ptr, indexStatPtr);
+  const IndexStatImplReq* impl_req = &indexStatPtr.p->m_request;
+
+  IndexStatReq* req = (IndexStatReq*)signal->getDataPtrSend();
+
+  Uint32 requestInfo = 0;
+  DictSignal::setRequestType(requestInfo, requestType);
+  DictSignal::addRequestFlagsGlobal(requestInfo, op_ptr.p->m_requestInfo);
+
+  TableRecordPtr indexPtr;
+  c_tableRecordPool.getPtr(indexPtr, impl_req->indexId);
+
+  req->clientRef = reference();
+  req->clientData = op_ptr.p->op_key;
+  req->transId = trans_ptr.p->m_transId;
+  req->transKey = trans_ptr.p->trans_key;
+  req->requestInfo = requestInfo;
+  req->requestFlag = 0;
+  req->indexId = impl_req->indexId;
+  req->indexVersion = indexPtr.p->tableVersion;
+  req->tableId = impl_req->tableId;
+
+  sendSignal(reference(), GSN_INDEX_STAT_REQ,
+             signal, IndexStatReq::SignalLength, JBB);
+}
+
+void
+Dbdict::indexStat_fromIndexStat(Signal* signal, Uint32 op_key, Uint32 ret)
+{
+  jam();
+  D("indexStat_fromIndexStat");
+
+  SchemaOpPtr op_ptr;
+  IndexStatRecPtr indexStatPtr;
+
+  findSchemaOp(op_ptr, indexStatPtr, op_key);
+  ndbrequire(!op_ptr.isNull());
+  SchemaTransPtr trans_ptr = op_ptr.p->m_trans_ptr;
+
+  if (ret == 0) {
+    jam();
+    const IndexStatConf* conf =
+      (const IndexStatConf*)signal->getDataPtr();
+
+    ndbrequire(conf->transId == trans_ptr.p->m_transId);
+    ndbrequire(indexStatPtr.p->m_subOpIndex < indexStatPtr.p->m_subOpCount);
+    indexStatPtr.p->m_subOpIndex += 1;
+    createSubOps(signal, op_ptr);
+  } else {
+    jam();
+    const IndexStatRef* ref =
+      (const IndexStatRef*)signal->getDataPtr();
+
+    ErrorInfo error;
+    setError(error, ref);
+    abortSubOps(signal, op_ptr, error);
+  }
+}
+
+void
+Dbdict::indexStat_reply(Signal* signal, SchemaOpPtr op_ptr, ErrorInfo error)
+{
+  jam();
+
+  SchemaTransPtr& trans_ptr = op_ptr.p->m_trans_ptr;
+  IndexStatRecPtr indexStatPtr;
+  getOpRec(op_ptr, indexStatPtr);
+  const IndexStatImplReq* impl_req = &indexStatPtr.p->m_request;
+
+  D("indexStat_reply" << V(impl_req->indexId));
+
+  TableRecordPtr indexPtr;
+  c_tableRecordPool.getPtr(indexPtr, impl_req->indexId);
+
+  if (!hasError(error)) {
+    IndexStatConf* conf = (IndexStatConf*)signal->getDataPtrSend();
+    conf->senderRef = reference();
+    conf->clientData = op_ptr.p->m_clientData;
+    conf->transId = trans_ptr.p->m_transId;
+
+    Uint32 clientRef = op_ptr.p->m_clientRef;
+    sendSignal(clientRef, GSN_INDEX_STAT_CONF, signal,
+               IndexStatConf::SignalLength, JBB);
+  } else {
+    jam();
+    IndexStatRef* ref = (IndexStatRef*)signal->getDataPtrSend();
+    ref->senderRef = reference();
+    ref->clientData = op_ptr.p->m_clientData;
+    ref->transId = trans_ptr.p->m_transId;
+    getError(error, ref);
+
+    Uint32 clientRef = op_ptr.p->m_clientRef;
+    sendSignal(clientRef, GSN_INDEX_STAT_REF, signal,
+               IndexStatRef::SignalLength, JBB);
+  }
+}
+
+// IndexStat: PREPARE
+
+void
+Dbdict::indexStat_prepare(Signal* signal, SchemaOpPtr op_ptr)
+{
+  jam();
+  IndexStatRecPtr indexStatPtr;
+  getOpRec(op_ptr, indexStatPtr);
+  const IndexStatImplReq* impl_req = &indexStatPtr.p->m_request;
+
+  D("indexStat_prepare" << V(*op_ptr.p));
+
+  if (impl_req->requestType == IndexStatReq::RT_UPDATE_STAT ||
+      impl_req->requestType == IndexStatReq::RT_DELETE_STAT) {
+    // the main op of stat update or delete does nothing
+    sendTransConf(signal, op_ptr);
+    return;
+  }
+
+  indexStat_toLocalStat(signal, op_ptr);
+}
+
+void
+Dbdict::indexStat_toLocalStat(Signal* signal, SchemaOpPtr op_ptr)
+{
+  IndexStatRecPtr indexStatPtr;
+  getOpRec(op_ptr, indexStatPtr);
+  const IndexStatImplReq* impl_req = &indexStatPtr.p->m_request;
+  SchemaTransPtr trans_ptr = op_ptr.p->m_trans_ptr;
+
+  D("indexStat_toLocalStat");
+
+  TableRecordPtr indexPtr;
+  c_tableRecordPool.getPtr(indexPtr, impl_req->indexId);
+  ndbrequire(indexPtr.p->isOrderedIndex());
+
+  Callback c = {
+    safe_cast(&Dbdict::indexStat_fromLocalStat),
+    op_ptr.p->op_key
+  };
+  op_ptr.p->m_callback = c;
+
+  IndexStatImplReq* req = (IndexStatImplReq*)signal->getDataPtrSend();
+  *req = *impl_req;
+  req->senderRef = reference();
+  req->senderData = op_ptr.p->op_key;
+  ndbrequire(req->fragId == ZNIL);
+  ndbrequire(indexPtr.p->indexStatFragId != ZNIL);
+  BlockReference ref = 0;
+
+  switch (impl_req->requestType) {
+  case IndexStatReq::RT_SCAN_FRAG:
+    req->fragId = indexPtr.p->indexStatFragId;
+    if (indexPtr.p->indexStatNodeId != getOwnNodeId()) {
+      jam();
+      D("skip" << V(impl_req->requestType));
+      execute(signal, c, 0);
+      return;
+    }
+    ref = TRIX_REF;
+    break;
+
+  case IndexStatReq::RT_CLEAN_NEW:
+  case IndexStatReq::RT_CLEAN_OLD:
+  case IndexStatReq::RT_CLEAN_ALL:
+    /*
+     * Index stats "v4" does scan deletes via TRIX-SUMA.  But SUMA does
+     * only local scans so do it on all nodes.
+     */
+    req->fragId = ZNIL;
+    ref = TRIX_REF;
+    break;
+
+  case IndexStatReq::RT_DROP_HEAD:
+    req->fragId = indexPtr.p->indexStatFragId;
+    if (indexPtr.p->indexStatNodeId != getOwnNodeId()) {
+      jam();
+      D("skip" << V(impl_req->requestType));
+      execute(signal, c, 0);
+      return;
+    }
+    ref = TRIX_REF;
+    break;
+
+  case IndexStatReq::RT_START_MON:
+    req->fragId = indexPtr.p->indexStatFragId;
+    if (indexPtr.p->indexStatNodeId != getOwnNodeId()) {
+      jam();
+      req->fragId = ZNIL;
+    }
+    ref = DBTUX_REF;
+    break;
+
+  case IndexStatReq::RT_STOP_MON:
+    req->fragId = ZNIL;
+    ref = DBTUX_REF;
+    break;
+
+  default:
+    ndbrequire(false); // only sub-ops seen here
+    break;
+  }
+
+  sendSignal(ref, GSN_INDEX_STAT_IMPL_REQ,
+             signal, IndexStatImplReq::SignalLength, JBB);
+}
+
+void
+Dbdict::indexStat_fromLocalStat(Signal* signal, Uint32 op_key, Uint32 ret)
+{
+  SchemaOpPtr op_ptr;
+  IndexStatRecPtr indexStatPtr;
+  findSchemaOp(op_ptr, indexStatPtr, op_key);
+  ndbrequire(!op_ptr.isNull());
+  const IndexStatImplReq* impl_req = &indexStatPtr.p->m_request;
+
+  if (ret != 0) {
+    jam();
+    if (impl_req->requestType != IndexStatReq::RT_CLEAN_OLD &&
+        impl_req->requestType != IndexStatReq::RT_CLEAN_ALL &&
+        impl_req->requestType != IndexStatReq::RT_DROP_HEAD) {
+      jam();
+      setError(op_ptr, ret, __LINE__);
+      sendTransRef(signal, op_ptr);
+      return;
+    }
+    D("ignore failed index stat cleanup");
+  }
+  sendTransConf(signal, op_ptr);
+}
+
+// IndexStat: COMMIT
+
+void
+Dbdict::indexStat_commit(Signal* signal, SchemaOpPtr op_ptr)
+{
+  jam();
+  IndexStatRecPtr indexStatPtr;
+  getOpRec(op_ptr, indexStatPtr);
+  D("indexStat_commit" << *op_ptr.p);
+  sendTransConf(signal, op_ptr);
+}
+
+// IndexStat: COMPLETE
+
+void
+Dbdict::indexStat_complete(Signal* signal, SchemaOpPtr op_ptr)
+{
+  jam();
+  IndexStatRecPtr indexStatPtr;
+  getOpRec(op_ptr, indexStatPtr);
+  D("indexStat_complete" << *op_ptr.p);
+  sendTransConf(signal, op_ptr);
+}
+
+// IndexStat: ABORT
+
+void
+Dbdict::indexStat_abortParse(Signal* signal, SchemaOpPtr op_ptr)
+{
+  D("indexStat_abortParse" << *op_ptr.p);
+  // wl3600_todo
+  sendTransConf(signal, op_ptr);
+}
+
+void
+Dbdict::indexStat_abortPrepare(Signal* signal, SchemaOpPtr op_ptr)
+{
+  D("indexStat_abortPrepare" << *op_ptr.p);
+
+  // nothing to do, entire index table will be dropped
+  sendTransConf(signal, op_ptr);
+}
+
+// IndexStat: MISC
+
+void
+Dbdict::execINDEX_STAT_CONF(Signal* signal)
+{
+  jamEntry();
+  const IndexStatConf* conf = (const IndexStatConf*)signal->getDataPtr();
+  handleDictConf(signal, conf);
+}
+
+void
+Dbdict::execINDEX_STAT_REF(Signal* signal)
+{
+  jamEntry();
+  const IndexStatRef* ref = (const IndexStatRef*)signal->getDataPtr();
+  handleDictRef(signal, ref);
+}
+
+void
+Dbdict::execINDEX_STAT_IMPL_CONF(Signal* signal)
+{
+  jamEntry();
+  const IndexStatImplConf* conf = (const IndexStatImplConf*)signal->getDataPtr();
+  handleDictConf(signal, conf);
+}
+
+void
+Dbdict::execINDEX_STAT_IMPL_REF(Signal* signal)
+{
+  jamEntry();
+  const IndexStatImplRef* ref = (const IndexStatImplRef*)signal->getDataPtr();
+  handleDictRef(signal, ref);
+}
+
+// IndexStat: background processing
+
+/*
+ * Receive report that an index needs stats update.  Request to
+ * non-master is sent to master.  Index is marked for stats update.
+ * Invalid request is simply ignored.  Master-NF really need not be
+ * handled but could be, by broadcasting all reports to all DICTs.
+ */
+void
+Dbdict::execINDEX_STAT_REP(Signal* signal)
+{
+  const IndexStatRep* rep = (const IndexStatRep*)signal->getDataPtr();
+
+  // non-master
+  if (c_masterNodeId != getOwnNodeId()) {
+    jam();
+    BlockReference dictRef = calcDictBlockRef(c_masterNodeId);
+    sendSignal(dictRef, GSN_INDEX_STAT_REP, signal,
+               IndexStatRep::SignalLength, JBB);
+    return;
+  }
+
+  // check
+  TableRecordPtr indexPtr;
+  if (rep->indexId >= c_tableRecordPool.getSize()) {
+    jam();
+    return;
+  }
+  XSchemaFile* xsf = &c_schemaFile[SchemaRecord::NEW_SCHEMA_FILE];
+  const SchemaFile::TableEntry* te = getTableEntry(xsf, rep->indexId);
+  if (te->m_tableState != SchemaFile::SF_IN_USE) {
+    jam();
+    return;
+  }
+  c_tableRecordPool.getPtr(indexPtr, rep->indexId);
+  if (rep->indexVersion != 0 &&
+      rep->indexVersion != indexPtr.p->tableVersion) {
+    jam();
+    return;
+  }
+  if (!indexPtr.p->isOrderedIndex()) {
+    jam();
+    return;
+  }
+  if (rep->requestType != IndexStatRep::RT_UPDATE_REQ) {
+    jam();
+    return;
+  }
+
+  D("index stat: " << copyRope<MAX_TAB_NAME_SIZE>(indexPtr.p->tableName)
+    << " request type:" << rep->requestType);
+
+  infoEvent("DICT: index %u stats auto-update requested", indexPtr.i);
+  indexPtr.p->indexStatBgRequest = rep->requestType;
+}
+
+void
+Dbdict::indexStatBg_process(Signal* signal)
+{
+  if (!c_indexStatAutoUpdate ||
+      c_masterNodeId != getOwnNodeId() ||
+      getNodeState().startLevel != NodeState::SL_STARTED) {
+    jam();
+    indexStatBg_sendContinueB(signal);
+    return;
+  }
+
+  D("indexStatBg_process" << V(c_indexStatBgId));
+  const uint maxloop = 32;
+  uint loop;
+  for (loop = 0; loop < maxloop; loop++, c_indexStatBgId++) {
+    jam();
+    c_indexStatBgId %= c_tableRecordPool.getSize();
+
+    // check
+    TableRecordPtr indexPtr;
+    XSchemaFile* xsf = &c_schemaFile[SchemaRecord::NEW_SCHEMA_FILE];
+    const SchemaFile::TableEntry* te = getTableEntry(xsf, c_indexStatBgId);
+    if (te->m_tableState != SchemaFile::SF_IN_USE) {
+      jam();
+      continue;
+    }
+    c_tableRecordPool.getPtr(indexPtr, c_indexStatBgId);
+    if (!indexPtr.p->isOrderedIndex()) {
+      jam();
+      continue;
+    }
+    if (indexPtr.p->indexStatBgRequest == 0) {
+      jam();
+      continue;
+    }
+    ndbrequire(indexPtr.p->indexStatBgRequest == IndexStatRep::RT_UPDATE_REQ);
+
+    TxHandlePtr tx_ptr;
+    if (!seizeTxHandle(tx_ptr)) {
+      jam();
+      return; // wait for one
+    }
+    Callback c = {
+      safe_cast(&Dbdict::indexStatBg_fromBeginTrans),
+      tx_ptr.p->tx_key
+    };
+    tx_ptr.p->m_callback = c;
+    beginSchemaTrans(signal, tx_ptr);
+    return;
+  }
+
+  indexStatBg_sendContinueB(signal);
+}
+
+void
+Dbdict::indexStatBg_fromBeginTrans(Signal* signal, Uint32 tx_key, Uint32 ret)
+{
+  D("indexStatBg_fromBeginTrans" << V(c_indexStatBgId) << V(tx_key) << V(ret));
+
+  TxHandlePtr tx_ptr;
+  findTxHandle(tx_ptr, tx_key);
+  ndbrequire(!tx_ptr.isNull());
+
+  TableRecordPtr indexPtr;
+  c_tableRecordPool.getPtr(indexPtr, c_indexStatBgId);
+
+  if (ret != 0) {
+    jam();
+    indexStatBg_sendContinueB(signal);
+    return;
+  }
+
+  Callback c = {
+    safe_cast(&Dbdict::indexStatBg_fromIndexStat),
+    tx_ptr.p->tx_key
+  };
+  tx_ptr.p->m_callback = c;
+
+  IndexStatReq* req = (IndexStatReq*)signal->getDataPtrSend();
+  req->clientRef = reference();
+  req->clientData = tx_ptr.p->tx_key;
+  req->transId = tx_ptr.p->m_transId;
+  req->transKey = tx_ptr.p->m_transKey;
+  req->requestInfo = IndexStatReq::RT_UPDATE_STAT;
+  req->requestFlag = 0;
+  req->indexId = c_indexStatBgId;
+  req->indexVersion = indexPtr.p->tableVersion;
+  req->tableId = indexPtr.p->primaryTableId;
+  sendSignal(reference(), GSN_INDEX_STAT_REQ,
+             signal, IndexStatReq::SignalLength, JBB);
+}
+
+void
+Dbdict::indexStatBg_fromIndexStat(Signal* signal, Uint32 tx_key, Uint32 ret)
+{
+  D("indexStatBg_fromIndexStat" << V(c_indexStatBgId) << V(tx_key) << (ret));
+
+  TxHandlePtr tx_ptr;
+  findTxHandle(tx_ptr, tx_key);
+  ndbrequire(!tx_ptr.isNull());
+
+  TableRecordPtr indexPtr;
+  c_tableRecordPool.getPtr(indexPtr, c_indexStatBgId);
+
+  if (ret != 0) {
+    jam();
+    setError(tx_ptr.p->m_error, ret, __LINE__);
+    warningEvent("DICT: index %u stats auto-update error: %d", indexPtr.i, ret);
+  }
+
+  Callback c = {
+    safe_cast(&Dbdict::indexStatBg_fromEndTrans),
+    tx_ptr.p->tx_key
+  };
+  tx_ptr.p->m_callback = c;
+
+  Uint32 flags = 0;
+  if (hasError(tx_ptr.p->m_error))
+    flags |= SchemaTransEndReq::SchemaTransAbort;
+  endSchemaTrans(signal, tx_ptr, flags);
+}
+
+void
+Dbdict::indexStatBg_fromEndTrans(Signal* signal, Uint32 tx_key, Uint32 ret)
+{
+  D("indexStatBg_fromEndTrans" << V(c_indexStatBgId) << V(tx_key) << (ret));
+
+  TxHandlePtr tx_ptr;
+  findTxHandle(tx_ptr, tx_key);
+  ndbrequire(!tx_ptr.isNull());
+
+  TableRecordPtr indexPtr;
+  c_tableRecordPool.getPtr(indexPtr, c_indexStatBgId);
+
+  if (ret != 0) {
+    jam();
+    // skip over but leave the request on
+    warningEvent("DICT: index %u stats auto-update error: %d", indexPtr.i, ret);
+  } else {
+    jam();
+    // mark request done
+    indexPtr.p->indexStatBgRequest = 0;
+    infoEvent("DICT: index %u stats auto-update done", indexPtr.i);
+  }
+
+  releaseTxHandle(tx_ptr);
+  c_indexStatBgId++;
+  indexStatBg_sendContinueB(signal);
+}
+
+void
+Dbdict::indexStatBg_sendContinueB(Signal* signal)
+{
+  D("indexStatBg_sendContinueB" << V(c_indexStatBgId));
+  signal->theData[0] = ZINDEX_STAT_BG_PROCESS;
+  sendSignalWithDelay(reference(), GSN_CONTINUEB, signal, 1000, 1);
+}
+
+// IndexStat: END
+
 // MODULE: CopyData
 
 const Dbdict::OpInfo
@@ -23058,6 +24022,7 @@ Dbdict::g_opInfoList[] = {
   &Dbdict::DropIndexRec::g_opInfo,
   &Dbdict::AlterIndexRec::g_opInfo,
   &Dbdict::BuildIndexRec::g_opInfo,
+  &Dbdict::IndexStatRec::g_opInfo,
   &Dbdict::CreateFilegroupRec::g_opInfo,
   &Dbdict::CreateFileRec::g_opInfo,
   &Dbdict::DropFilegroupRec::g_opInfo,
@@ -23273,7 +24238,7 @@ Dbdict::seizeSchemaOp(SchemaOpPtr& op_pt
         D("seizeSchemaOp" << V(op_key) << V(info.m_opType));
         return true;
       }
-      c_schemaOpHash.release(op_ptr);
+      c_schemaOpPool.release(op_ptr);
     }
   }
   op_ptr.setNull();

=== modified file 'storage/ndb/src/kernel/blocks/dbdict/Dbdict.hpp'
--- a/storage/ndb/src/kernel/blocks/dbdict/Dbdict.hpp	2011-05-24 05:27:15 +0000
+++ b/storage/ndb/src/kernel/blocks/dbdict/Dbdict.hpp	2011-05-25 15:03:11 +0000
@@ -53,6 +53,7 @@
 #include <signaldata/AlterIndxImpl.hpp>
 #include <signaldata/BuildIndx.hpp>
 #include <signaldata/BuildIndxImpl.hpp>
+#include <signaldata/IndexStatSignal.hpp>
 #include <signaldata/UtilPrepare.hpp>
 #include <signaldata/CreateEvnt.hpp>
 #include <signaldata/CreateTrig.hpp>
@@ -94,6 +95,7 @@
 #define ZDICT_TAKEOVER_REQ 5
 
 #define ZCOMMIT_WAIT_GCI   6
+#define ZINDEX_STAT_BG_PROCESS 7
 
 /*--------------------------------------------------------------*/
 // Other constants in alphabetical order
@@ -405,6 +407,18 @@ public:
      * Access rights to table during single user mode
      */
     Uint8 singleUserMode;
+
+    /*
+     * Fragment and node to use for index statistics.  Not part of
+     * DICTTABINFO.  Computed locally by each DICT.  If the node is
+     * down, no automatic stats update takes place.  This is not
+     * critical and is not worth fixing.
+     */
+    Uint16 indexStatFragId;
+    Uint16 indexStatNodeId;
+
+    // pending background request (IndexStatRep::RequestType)
+    Uint32 indexStatBgRequest;
   };
 
   typedef Ptr<TableRecord> TableRecordPtr;
@@ -920,6 +934,14 @@ private:
   void execDICT_TAKEOVER_REF(Signal* signal);
   void execDICT_TAKEOVER_CONF(Signal* signal);
 
+  // ordered index statistics
+  void execINDEX_STAT_REQ(Signal* signal);
+  void execINDEX_STAT_CONF(Signal* signal);
+  void execINDEX_STAT_REF(Signal* signal);
+  void execINDEX_STAT_IMPL_CONF(Signal* signal);
+  void execINDEX_STAT_IMPL_REF(Signal* signal);
+  void execINDEX_STAT_REP(Signal* signal);
+
   /*
    *  2.4 COMMON STORED VARIABLES
    */
@@ -2615,10 +2637,12 @@ private:
       return dict->c_alterIndexRecPool;
     }
 
-    // sub-operation counters
+    // sub-operation counters (true = done or skip)
     const TriggerTmpl* m_triggerTmpl;
     bool m_sub_trigger;
     bool m_sub_build_index;
+    bool m_sub_index_stat_dml;
+    bool m_sub_index_stat_mon;
 
     // prepare phase
     bool m_tc_index_done;
@@ -2633,9 +2657,11 @@ private:
       memset(&m_attrList, 0, sizeof(m_attrList));
       m_attrMask.clear();
       m_triggerTmpl = 0;
+      m_sub_trigger = false;
       m_sub_build_index = false;
+      m_sub_index_stat_dml = false;
+      m_sub_index_stat_mon = false;
       m_tc_index_done = false;
-      m_sub_trigger = false;
     }
 
 #ifdef VM_TRACE
@@ -2662,6 +2688,9 @@ private:
   void alterIndex_abortParse(Signal*, SchemaOpPtr);
   void alterIndex_abortPrepare(Signal*, SchemaOpPtr);
 
+  // parse phase sub-routine
+  void set_index_stat_frag(Signal*, TableRecordPtr indexPtr);
+
   // sub-ops
   void alterIndex_toCreateTrigger(Signal*, SchemaOpPtr);
   void alterIndex_atCreateTrigger(Signal*, SchemaOpPtr);
@@ -2671,6 +2700,8 @@ private:
   void alterIndex_fromDropTrigger(Signal*, Uint32 op_key, Uint32 ret);
   void alterIndex_toBuildIndex(Signal*, SchemaOpPtr);
   void alterIndex_fromBuildIndex(Signal*, Uint32 op_key, Uint32 ret);
+  void alterIndex_toIndexStat(Signal*, SchemaOpPtr);
+  void alterIndex_fromIndexStat(Signal*, Uint32 op_key, Uint32 ret);
 
   // prepare phase
   void alterIndex_toCreateLocal(Signal*, SchemaOpPtr);
@@ -2762,6 +2793,69 @@ private:
   void buildIndex_toLocalOnline(Signal*, SchemaOpPtr);
   void buildIndex_fromLocalOnline(Signal*, Uint32 op_key, Uint32 ret);
 
+  // MODULE: IndexStat
+
+  struct IndexStatRec : public OpRec {
+    static const OpInfo g_opInfo;
+
+    static ArrayPool<Dbdict::IndexStatRec>&
+    getPool(Dbdict* dict) {
+      return dict->c_indexStatRecPool;
+    }
+
+    IndexStatImplReq m_request;
+
+    // sub-operation counters
+    const TriggerTmpl* m_triggerTmpl;
+    Uint32 m_subOpCount;
+    Uint32 m_subOpIndex;
+
+    IndexStatRec() :
+      OpRec(g_opInfo, (Uint32*)&m_request) {
+      memset(&m_request, 0, sizeof(m_request));
+      m_subOpCount = 0;
+      m_subOpIndex = 0;
+    }
+  };
+
+  typedef Ptr<IndexStatRec> IndexStatRecPtr;
+  ArrayPool<IndexStatRec> c_indexStatRecPool;
+
+  Uint32 c_indexStatAutoCreate;
+  Uint32 c_indexStatAutoUpdate;
+  Uint32 c_indexStatBgId;
+
+  // OpInfo
+  bool indexStat_seize(SchemaOpPtr);
+  void indexStat_release(SchemaOpPtr);
+  //
+  void indexStat_parse(Signal*, bool master,
+                        SchemaOpPtr, SectionHandle&, ErrorInfo&);
+  bool indexStat_subOps(Signal*, SchemaOpPtr);
+  void indexStat_reply(Signal*, SchemaOpPtr, ErrorInfo);
+  //
+  void indexStat_prepare(Signal*, SchemaOpPtr);
+  void indexStat_commit(Signal*, SchemaOpPtr);
+  void indexStat_complete(Signal*, SchemaOpPtr);
+  //
+  void indexStat_abortParse(Signal*, SchemaOpPtr);
+  void indexStat_abortPrepare(Signal*, SchemaOpPtr);
+
+  // parse phase
+  void indexStat_toIndexStat(Signal*, SchemaOpPtr, Uint32 requestType);
+  void indexStat_fromIndexStat(Signal*, Uint32 op_key, Uint32 ret);
+
+  // prepare phase
+  void indexStat_toLocalStat(Signal*, SchemaOpPtr);
+  void indexStat_fromLocalStat(Signal*, Uint32 op_key, Uint32 ret);
+
+  // background processing of stat requests
+  void indexStatBg_process(Signal*);
+  void indexStatBg_fromBeginTrans(Signal*, Uint32 tx_key, Uint32 ret);
+  void indexStatBg_fromIndexStat(Signal*, Uint32 tx_key, Uint32 ret);
+  void indexStatBg_fromEndTrans(Signal*, Uint32 tx_key, Uint32 ret);
+  void indexStatBg_sendContinueB(Signal*);
+
   // MODULE: CreateHashMap
 
   struct HashMapRecord {

=== modified file 'storage/ndb/src/kernel/blocks/dblqh/Dblqh.hpp'
--- a/storage/ndb/src/kernel/blocks/dblqh/Dblqh.hpp	2011-05-25 14:31:47 +0000
+++ b/storage/ndb/src/kernel/blocks/dblqh/Dblqh.hpp	2011-05-29 10:55:32 +0000
@@ -563,6 +563,8 @@ public:
     Uint8 scanKeyinfoFlag;
     Uint8 m_last_row;
     Uint8 m_reserved;
+    Uint8 statScan;
+    Uint8 dummy[3]; // align?
   }; // Size 272 bytes
   typedef Ptr<ScanRecord> ScanRecordPtr;
 

=== modified file 'storage/ndb/src/kernel/blocks/dblqh/DblqhMain.cpp'
--- a/storage/ndb/src/kernel/blocks/dblqh/DblqhMain.cpp	2011-05-30 08:50:17 +0000
+++ b/storage/ndb/src/kernel/blocks/dblqh/DblqhMain.cpp	2011-05-31 08:28:58 +0000
@@ -3397,6 +3397,8 @@ Dblqh::execREAD_PSEUDO_REQ(Signal* signa
     break;
   }
   case AttributeHeader::RECORDS_IN_RANGE:
+  case AttributeHeader::INDEX_STAT_KEY:
+  case AttributeHeader::INDEX_STAT_VALUE:
   {
     jam();
     // scanptr gets reset somewhere within the timeslice
@@ -10458,6 +10460,7 @@ void Dblqh::continueAfterReceivingAllAiL
   AccScanReq::setLockMode(req->requestInfo, scanptr.p->scanLockMode);
   AccScanReq::setReadCommittedFlag(req->requestInfo, scanptr.p->readCommitted);
   AccScanReq::setDescendingFlag(req->requestInfo, scanptr.p->descending);
+  AccScanReq::setStatScanFlag(req->requestInfo, scanptr.p->statScan);
 
   if (refToMain(tcConnectptr.p->clientBlockref) == BACKUP)
   {
@@ -11529,6 +11532,7 @@ Uint32 Dblqh::initScanrec(const ScanFrag
   scanptr.p->descending = descending;
   scanptr.p->tupScan = tupScan;
   scanptr.p->lcpScan = ScanFragReq::getLcpScanFlag(reqinfo);
+  scanptr.p->statScan = ScanFragReq::getStatScanFlag(reqinfo);
   scanptr.p->scanState = ScanRecord::SCAN_FREE;
   scanptr.p->scanFlag = ZFALSE;
   scanptr.p->m_row_id.setNull();

=== modified file 'storage/ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp'
--- a/storage/ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp	2011-05-17 23:29:55 +0000
+++ b/storage/ndb/src/kernel/blocks/dbtup/DbtupRoutines.cpp	2011-05-25 15:03:11 +0000
@@ -2476,7 +2476,8 @@ Dbtup::read_pseudo(const Uint32 * inBuff
   Uint32* outBuffer = outBuf + ((outPos - 1) >> 2);
   
   Uint32 sz;
-  SignalT<4> signalT;
+  const Uint32 DataSz = MAX_INDEX_STAT_KEY_SIZE;
+  SignalT<DataSz> signalT;
   Signal * signal = new (&signalT) Signal(0);
   bzero(signal, sizeof(signalT));
   switch(attrId){
@@ -2546,6 +2547,23 @@ Dbtup::read_pseudo(const Uint32 * inBuff
     outBuffer[4] = signal->theData[3];
     sz = 4;
     break;
+  case AttributeHeader::INDEX_STAT_KEY:
+  case AttributeHeader::INDEX_STAT_VALUE:
+  {
+    signal->theData[0] = req_struct->operPtrP->userpointer;
+    signal->theData[1] = attrId;
+
+    EXECUTE_DIRECT(DBLQH, GSN_READ_PSEUDO_REQ, signal, 2);
+
+    const Uint8* src = (Uint8*)&signal->theData[0];
+    Uint32 byte_sz = 2 + src[0] + (src[1] << 8);
+    Uint8* dst = (Uint8*)&outBuffer[1];
+    memcpy(dst, src, byte_sz);
+    while (byte_sz % 4 != 0)
+      dst[byte_sz++] = 0;
+    sz = byte_sz / 4;
+    break;
+  }
   case AttributeHeader::ROWID:
     outBuffer[1] = req_struct->frag_page_id;
     outBuffer[2] = req_struct->operPtrP->m_tuple_location.m_page_idx;

=== modified file 'storage/ndb/src/kernel/blocks/dbtux/Dbtux.hpp'
--- a/storage/ndb/src/kernel/blocks/dbtux/Dbtux.hpp	2011-05-17 12:19:20 +0000
+++ b/storage/ndb/src/kernel/blocks/dbtux/Dbtux.hpp	2011-05-19 09:40:36 +0000
@@ -45,6 +45,7 @@
 #include <signaldata/NextScan.hpp>
 #include <signaldata/AccLock.hpp>
 #include <signaldata/DumpStateOrd.hpp>
+#include <signaldata/IndexStatSignal.hpp>
 
 // debug
 #ifdef VM_TRACE
@@ -236,7 +237,6 @@ private:
     Uint8 m_prefSize;           // words in min prefix
     Uint8 m_minOccup;           // min entries in internal node
     Uint8 m_maxOccup;           // max entries in node
-    Uint32 m_entryCount;        // stat: current entries
     TupLoc m_root;              // root node
     TreeHead();
     // methods
@@ -386,6 +386,7 @@ private:
     TreePos m_scanPos;          // position
     TreeEnt m_scanEnt;          // latest entry found
     Uint32 m_nodeScan;          // next scan at node (single-linked)
+    Uint32 m_statOpPtrI;        // RNIL unless this is a statistics scan
     union {
     Uint32 nextPool;
     Uint32 nextList;
@@ -425,6 +426,8 @@ private:
     Uint16 m_prefAttrs;         // attributes in min prefix
     Uint16 m_prefBytes;         // max bytes in min prefix
     KeySpec m_keySpec;
+    Uint32 m_statFragPtrI;      // fragment to monitor if not RNIL
+    Uint32 m_statLoadTime;      // load time of index stats
     union {
     bool m_storeNullKey;
     Uint32 nextPool;
@@ -453,6 +456,9 @@ private:
     Uint32 m_tupIndexFragPtrI;
     Uint32 m_tupTableFragPtrI;
     Uint32 m_accTableFragPtrI;
+    Uint64 m_entryCount;        // current entries
+    Uint64 m_entryBytes;        // sum of index key sizes
+    Uint64 m_entryOps;          // ops since last index stats update
     union {
     Uint32 nextPool;
     };
@@ -519,6 +525,76 @@ private:
     void progError(int line, int cause, const char* file);
   };
 
+  // stats scan
+  struct StatOp;
+  friend struct StatOp;
+  struct StatOp {
+    // the scan
+    Uint32 m_scanOpPtrI;
+    // parameters
+    Uint32 m_saveSize;
+    Uint32 m_saveScale;
+    Uint32 m_batchSize;
+    Uint32 m_estBytes;
+   // counters
+   Uint32 m_rowCount;
+   Uint32 m_batchCurr;
+   bool m_haveSample;
+   Uint32 m_sampleCount;
+   Uint32 m_keyBytes;
+   bool m_keyChange;
+   bool m_usePrev;
+   // metadata
+   enum { MaxKeyCount = MAX_INDEX_STAT_KEY_COUNT };
+   enum { MaxKeySize = MAX_INDEX_STAT_KEY_SIZE };
+   enum { MaxValueCount = MAX_INDEX_STAT_VALUE_COUNT };
+   enum { MaxValueSize = MAX_INDEX_STAT_VALUE_SIZE };
+   Uint32 m_keyCount;
+   Uint32 m_valueCount;
+   // pack
+   const KeySpec& m_keySpec;
+   NdbPack::Spec m_valueSpec;
+   NdbPack::Type m_valueSpecBuf[MaxValueCount];
+   // data previous current result
+   KeyData m_keyData1;
+   KeyData m_keyData2;
+   KeyData m_keyData;
+   NdbPack::Data m_valueData;
+   // buffers with one word for length bytes
+   Uint32 m_keyDataBuf1[1 + MaxKeySize];
+   Uint32 m_keyDataBuf2[1 + MaxKeySize];
+   Uint32 m_keyDataBuf[1 + MaxKeySize];
+   Uint32 m_valueDataBuf[1 + MaxValueCount];
+   // value collection
+   struct Value {
+     Uint32 m_rir;
+     Uint32 m_unq[MaxKeyCount];
+     Value();
+   };
+   Value m_value1;
+   Value m_value2;
+   union {
+   Uint32 nextPool;
+   };
+   StatOp(const Index&);
+  };
+  typedef Ptr<StatOp> StatOpPtr;
+  ArrayPool<StatOp> c_statOpPool;
+  RSS_AP_SNAPSHOT(c_statOpPool);
+
+  // stats monitor (shared by req data and continueB loop)
+  struct StatMon;
+  friend struct StatMon;
+  struct StatMon {
+    IndexStatImplReq m_req;
+    Uint32 m_requestType;
+    // continueB loop
+    Uint32 m_loopIndexId;
+    Uint32 m_loopDelay;
+    StatMon();
+  };
+  StatMon c_statMon;
+
   // methods
 
   /*
@@ -644,9 +720,26 @@ private:
    * DbtuxStat.cpp
    */
   void execREAD_PSEUDO_REQ(Signal* signal);
+  // one-round-trip tree-dive records in range
   void statRecordsInRange(ScanOpPtr scanPtr, Uint32* out);
   Uint32 getEntriesBeforeOrAfter(Frag& frag, TreePos pos, unsigned idir);
   unsigned getPathToNode(NodeHandle node, Uint16* path);
+  // stats scan
+  int statScanInit(StatOpPtr, const Uint32* data, Uint32 len, Uint32* usedLen);
+  int statScanAddRow(StatOpPtr, TreeEnt ent);
+  void statScanReadKey(StatOpPtr, Uint32* out);
+  void statScanReadValue(StatOpPtr, Uint32* out);
+  void execINDEX_STAT_REP(Signal*); // from TRIX
+  // stats monitor request
+  void execINDEX_STAT_IMPL_REQ(Signal*);
+  void statMonStart(Signal*, StatMon&);
+  void statMonStop(Signal*, StatMon&);
+  void statMonConf(Signal*, StatMon&);
+  // stats monitor continueB loop
+  void statMonSendContinueB(Signal*);
+  void statMonExecContinueB(Signal*);
+  void statMonCheck(Signal*, StatMon&);
+  void statMonRep(Signal*, StatMon&);
 
   /*
    * DbtuxDebug.cpp
@@ -676,6 +769,8 @@ private:
   friend class NdbOut& operator<<(NdbOut&, const Frag&);
   friend class NdbOut& operator<<(NdbOut&, const FragOp&);
   friend class NdbOut& operator<<(NdbOut&, const NodeHandle&);
+  friend class NdbOut& operator<<(NdbOut&, const StatOp&);
+  friend class NdbOut& operator<<(NdbOut&, const StatMon&);
   FILE* debugFile;
   NdbOut debugOut;
   unsigned debugFlags;
@@ -684,7 +779,8 @@ private:
     DebugMaint = 2,             // log maintenance ops
     DebugTree = 4,              // log and check tree after each op
     DebugScan = 8,              // log scans
-    DebugLock = 16              // log ACC locks
+    DebugLock = 16,             // log ACC locks
+    DebugStat = 32              // log stats collection
   };
   STATIC_CONST( DataFillByte = 0xa2 );
   STATIC_CONST( NodeFillByte = 0xa4 );
@@ -722,6 +818,14 @@ private:
 
   struct TuxCtx c_ctx; // Global Tux context, for everything build MT-index build
 
+  // index stats
+  bool c_indexStatAutoUpdate;
+  Uint32 c_indexStatSaveSize;
+  Uint32 c_indexStatSaveScale;
+  Uint32 c_indexStatTriggerPct;
+  Uint32 c_indexStatTriggerScale;
+  Uint32 c_indexStatUpdateDelay;
+
   // inlined utils
   Uint32 getDescSize(const Index& index);
   DescHead& getDescHead(const Index& index);
@@ -878,7 +982,6 @@ Dbtux::TreeHead::TreeHead() :
   m_prefSize(0),
   m_minOccup(0),
   m_maxOccup(0),
-  m_entryCount(0),
   m_root()
 {
 }
@@ -956,7 +1059,8 @@ Dbtux::ScanOp::ScanOp() :
   m_scanBound(),
   m_scanPos(),
   m_scanEnt(),
-  m_nodeScan(RNIL)
+  m_nodeScan(RNIL),
+  m_statOpPtrI(RNIL)
 {
 }
 
@@ -974,6 +1078,8 @@ Dbtux::Index::Index() :
   m_prefAttrs(0),
   m_prefBytes(0),
   m_keySpec(),
+  m_statFragPtrI(RNIL),
+  m_statLoadTime(0),
   m_storeNullKey(false)
 {
   for (unsigned i = 0; i < MaxIndexFragments; i++) {
@@ -994,7 +1100,10 @@ Dbtux::Frag::Frag(ArrayPool<ScanOp>& sca
   m_scanList(scanOpPool),
   m_tupIndexFragPtrI(RNIL),
   m_tupTableFragPtrI(RNIL),
-  m_accTableFragPtrI(RNIL)
+  m_accTableFragPtrI(RNIL),
+  m_entryCount(0),
+  m_entryBytes(0),
+  m_entryOps(0)
 {
 }
 
@@ -1146,6 +1255,59 @@ Dbtux::NodeHandle::getEnt(unsigned pos)
   return entList[pos];
 }
 
+// stats
+
+inline
+Dbtux::StatOp::Value::Value()
+{
+  m_rir = 0;
+  Uint32 i;
+  for (i = 0; i < MaxKeyCount; i++)
+    m_unq[i] = 0;
+}
+
+inline
+Dbtux::StatOp::StatOp(const Index& index) :
+  m_scanOpPtrI(RNIL),
+  m_saveSize(0),
+  m_saveScale(0),
+  m_batchSize(0),
+  m_estBytes(0),
+  m_rowCount(0),
+  m_batchCurr(0),
+  m_haveSample(false),
+  m_sampleCount(0),
+  m_keyBytes(0),
+  m_keyChange(false),
+  m_usePrev(false),
+  m_keyCount(0),
+  m_valueCount(0),
+  m_keySpec(index.m_keySpec),
+  m_keyData1(m_keySpec, false, 2),
+  m_keyData2(m_keySpec, false, 2),
+  m_keyData(m_keySpec, false, 2),
+  m_valueData(m_valueSpec, false, 2),
+  m_value1(),
+  m_value2()
+{
+  m_valueSpec.set_buf(m_valueSpecBuf, MaxValueCount);
+  m_keyData1.set_buf(m_keyDataBuf1, sizeof(m_keyDataBuf1));
+  m_keyData2.set_buf(m_keyDataBuf2, sizeof(m_keyDataBuf2));
+  m_keyData.set_buf(m_keyDataBuf, sizeof(m_keyDataBuf));
+  m_valueData.set_buf(m_valueDataBuf, sizeof(m_valueDataBuf));
+}
+
+// Dbtux::StatMon
+
+inline
+Dbtux::StatMon::StatMon() :
+  m_requestType(0),
+  m_loopIndexId(0),
+  m_loopDelay(1000)
+{
+  memset(&m_req, 0, sizeof(m_req));
+}
+
 // parameters for methods
 
 #ifdef VM_TRACE

=== modified file 'storage/ndb/src/kernel/blocks/dbtux/DbtuxBuild.cpp'
--- a/storage/ndb/src/kernel/blocks/dbtux/DbtuxBuild.cpp	2011-05-17 12:19:20 +0000
+++ b/storage/ndb/src/kernel/blocks/dbtux/DbtuxBuild.cpp	2011-05-19 09:40:36 +0000
@@ -139,6 +139,9 @@ Dbtux::mt_buildIndexFragment(mt_BuildInd
       ndbrequire(frag.m_freeLoc != NullTupLoc);
     }
     treeAdd(ctx, frag, treePos, ent);
+    frag.m_entryCount++;
+    frag.m_entryBytes += searchKey.get_data_len();
+    frag.m_entryOps++;
   }
 
   if (err < 0)

=== modified file 'storage/ndb/src/kernel/blocks/dbtux/DbtuxDebug.cpp'
--- a/storage/ndb/src/kernel/blocks/dbtux/DbtuxDebug.cpp	2011-05-30 08:50:17 +0000
+++ b/storage/ndb/src/kernel/blocks/dbtux/DbtuxDebug.cpp	2011-05-31 08:28:58 +0000
@@ -125,7 +125,7 @@ void Dbtux::execDBINFO_SCANREQ(Signal *s
 
 /*
  * 12001 log file 0-close 1-open 2-append 3-append to signal log
- * 12002 log flags 1-meta 2-maint 4-tree 8-scan
+ * 12002 log flags 1-meta 2-maint 4-tree 8-scan lock-16 stat-32
  */
 void
 Dbtux::execDUMP_STATE_ORD(Signal* signal)
@@ -425,6 +425,8 @@ operator<<(NdbOut& out, const Dbtux::Sca
   out << " [savePointId " << dec << scan.m_savePointId << "]";
   out << " [accLockOp " << hex << scan.m_accLockOp << "]";
   out << " [accLockOps";
+  if (globalData.isNdbMtLqh)//TODO
+    return out;
   {
     DLFifoList<Dbtux::ScanLock>::Head head = scan.m_accLockOps;
     LocalDLFifoList<Dbtux::ScanLock> list(tux->c_scanLockPool, head);
@@ -461,6 +463,8 @@ operator<<(NdbOut& out, const Dbtux::Ind
   out << "[Index " << hex << &index;
   out << " [tableId " << dec << index.m_tableId << "]";
   out << " [numFrags " << dec << index.m_numFrags << "]";
+  if (globalData.isNdbMtLqh)//TODO
+    return out;
   for (unsigned i = 0; i < index.m_numFrags; i++) {
     out << " [frag " << dec << i << " ";
     const Dbtux::Frag& frag = *tux->c_fragPool.getPtr(index.m_fragPtrI[i]);
@@ -472,6 +476,8 @@ operator<<(NdbOut& out, const Dbtux::Ind
   out << " [numAttrs " << dec << index.m_numAttrs << "]";
   out << " [prefAttrs " << dec << index.m_prefAttrs << "]";
   out << " [prefBytes " << dec << index.m_prefBytes << "]";
+  out << " [statFragPtrI " << hex << index.m_statFragPtrI << "]";
+  out << " [statLoadTime " << dec << index.m_statLoadTime << "]";
   out << "]";
   return out;
 }
@@ -483,6 +489,9 @@ operator<<(NdbOut& out, const Dbtux::Fra
   out << " [tableId " << dec << frag.m_tableId << "]";
   out << " [indexId " << dec << frag.m_indexId << "]";
   out << " [fragId " << dec << frag.m_fragId << "]";
+  out << " [entryCount " << dec << frag.m_entryCount << "]";
+  out << " [entryBytes " << dec << frag.m_entryBytes << "]";
+  out << " [entryOps " << dec << frag.m_entryOps << "]";
   out << " [tree " << frag.m_tree << "]";
   out << "]";
   return out;
@@ -525,5 +534,25 @@ operator<<(NdbOut& out, const Dbtux::Nod
   out << "]";
   return out;
 }
+
+NdbOut&
+operator<<(NdbOut& out, const Dbtux::StatOp& stat)
+{
+  out << "[StatOp " << hex << &stat;
+  out << " [saveSize " << dec << stat.m_saveSize << "]";
+  out << " [saveScale " << dec << stat.m_saveScale << "]";
+  out << " [batchSize " << dec << stat.m_batchSize << "]";
+  out << "]";
+  return out;
+}
+
+NdbOut&
+operator<<(NdbOut& out, const Dbtux::StatMon& mon)
+{
+  out << "[StatMon";
+  out << " [loopIndexId " << dec << mon.m_loopIndexId << "]";
+  out << "]";
+  return out;
+}
 
 #endif

=== modified file 'storage/ndb/src/kernel/blocks/dbtux/DbtuxGen.cpp'
--- a/storage/ndb/src/kernel/blocks/dbtux/DbtuxGen.cpp	2011-05-30 08:50:17 +0000
+++ b/storage/ndb/src/kernel/blocks/dbtux/DbtuxGen.cpp	2011-05-31 08:28:58 +0000
@@ -31,7 +31,13 @@ Dbtux::Dbtux(Block_context& ctx, Uint32
   debugFlags(0),
 #endif
   c_internalStartPhase(0),
-  c_typeOfStart(NodeState::ST_ILLEGAL_TYPE)
+  c_typeOfStart(NodeState::ST_ILLEGAL_TYPE),
+  c_indexStatAutoUpdate(false),
+  c_indexStatSaveSize(0),
+  c_indexStatSaveScale(0),
+  c_indexStatTriggerPct(0),
+  c_indexStatTriggerScale(0),
+  c_indexStatUpdateDelay(0)
 {
   BLOCK_CONSTRUCTOR(Dbtux);
   // verify size assumptions (also when release-compiled)
@@ -73,6 +79,8 @@ Dbtux::Dbtux(Block_context& ctx, Uint32
    * DbtuxStat.cpp
    */
   addRecSignal(GSN_READ_PSEUDO_REQ, &Dbtux::execREAD_PSEUDO_REQ);
+  addRecSignal(GSN_INDEX_STAT_REP, &Dbtux::execINDEX_STAT_REP);
+  addRecSignal(GSN_INDEX_STAT_IMPL_REQ, &Dbtux::execINDEX_STAT_IMPL_REQ);
   /*
    * DbtuxDebug.cpp
    */
@@ -102,6 +110,13 @@ Dbtux::execCONTINUEB(Signal* signal)
       dropIndex(signal, indexPtr, data[2], data[3]);
     }
     break;
+  case TuxContinueB::StatMon:
+    {
+      Uint32 id = data[1];
+      ndbrequire(id == c_statMon.m_loopIndexId);
+      statMonExecContinueB(signal);
+    }
+    break;
   default:
     ndbrequire(false);
     break;
@@ -143,8 +158,16 @@ Dbtux::execSTTOR(Signal* signal)
     jam();
     c_typeOfStart = signal->theData[7];
     break;
+    return;
   case 7:
     c_internalStartPhase = 6;
+    /*
+     * config cannot yet be changed dynamically but we start the
+     * loop always anyway because the cost is minimal
+     */
+    c_statMon.m_loopIndexId = 0;
+    statMonSendContinueB(signal);
+    break;
   default:
     jam();
     break;
@@ -190,6 +213,12 @@ Dbtux::execREAD_CONFIG_REQ(Signal* signa
   Uint32 nAttribute;
   Uint32 nScanOp; 
   Uint32 nScanBatch;
+  Uint32 nStatAutoUpdate;
+  Uint32 nStatSaveSize;
+  Uint32 nStatSaveScale;
+  Uint32 nStatTriggerPct;
+  Uint32 nStatTriggerScale;
+  Uint32 nStatUpdateDelay;
 
   const ndb_mgm_configuration_iterator * p = 
     m_ctx.m_config.getOwnConfigIterator();
@@ -200,10 +229,23 @@ Dbtux::execREAD_CONFIG_REQ(Signal* signa
   ndbrequire(!ndb_mgm_get_int_parameter(p, CFG_TUX_ATTRIBUTE, &nAttribute));
   ndbrequire(!ndb_mgm_get_int_parameter(p, CFG_TUX_SCAN_OP, &nScanOp));
   ndbrequire(!ndb_mgm_get_int_parameter(p, CFG_DB_BATCH_SIZE, &nScanBatch));
+  ndbrequire(!ndb_mgm_get_int_parameter(p, CFG_DB_INDEX_STAT_AUTO_UPDATE,
+                                        &nStatAutoUpdate));
+  ndbrequire(!ndb_mgm_get_int_parameter(p, CFG_DB_INDEX_STAT_SAVE_SIZE,
+                                        &nStatSaveSize));
+  ndbrequire(!ndb_mgm_get_int_parameter(p, CFG_DB_INDEX_STAT_SAVE_SCALE,
+                                        &nStatSaveScale));
+  ndbrequire(!ndb_mgm_get_int_parameter(p, CFG_DB_INDEX_STAT_TRIGGER_PCT,
+                                        &nStatTriggerPct));
+  ndbrequire(!ndb_mgm_get_int_parameter(p, CFG_DB_INDEX_STAT_TRIGGER_SCALE,
+                                        &nStatTriggerScale));
+  ndbrequire(!ndb_mgm_get_int_parameter(p, CFG_DB_INDEX_STAT_UPDATE_DELAY,
+                                        &nStatUpdateDelay));
 
   const Uint32 nDescPage = (nIndex * DescHeadSize + nAttribute * KeyTypeSize + nAttribute * AttributeHeaderSize + DescPageSize - 1) / DescPageSize;
   const Uint32 nScanBoundWords = nScanOp * ScanBoundSegmentSize * 4;
   const Uint32 nScanLock = nScanOp * nScanBatch;
+  const Uint32 nStatOp = 8;
   
   c_indexPool.setSize(nIndex);
   c_fragPool.setSize(nFragment);
@@ -212,6 +254,14 @@ Dbtux::execREAD_CONFIG_REQ(Signal* signa
   c_scanOpPool.setSize(nScanOp);
   c_scanBoundPool.setSize(nScanBoundWords);
   c_scanLockPool.setSize(nScanLock);
+  c_statOpPool.setSize(nStatOp);
+  c_indexStatAutoUpdate = nStatAutoUpdate;
+  c_indexStatSaveSize = nStatSaveSize;
+  c_indexStatSaveScale = nStatSaveScale;
+  c_indexStatTriggerPct = nStatTriggerPct;
+  c_indexStatTriggerScale = nStatTriggerScale;
+  c_indexStatUpdateDelay = nStatUpdateDelay;
+
   /*
    * Index id is physical array index.  We seize and initialize all
    * index records now.

=== modified file 'storage/ndb/src/kernel/blocks/dbtux/DbtuxMaint.cpp'
--- a/storage/ndb/src/kernel/blocks/dbtux/DbtuxMaint.cpp	2011-05-04 11:58:38 +0000
+++ b/storage/ndb/src/kernel/blocks/dbtux/DbtuxMaint.cpp	2011-05-19 09:40:36 +0000
@@ -128,6 +128,9 @@ Dbtux::execTUX_MAINT_REQ(Signal* signal)
       ndbrequire(frag.m_freeLoc != NullTupLoc);
     }
     treeAdd(c_ctx, frag, treePos, ent);
+    frag.m_entryCount++;
+    frag.m_entryBytes += searchKey.get_data_len();
+    frag.m_entryOps++;
     break;
   case TuxMaintReq::OpRemove:
     jam();
@@ -147,6 +150,10 @@ Dbtux::execTUX_MAINT_REQ(Signal* signal)
       break;
     }
     treeRemove(frag, treePos);
+    ndbrequire(frag.m_entryCount != 0);
+    frag.m_entryCount--;
+    frag.m_entryBytes -= searchKey.get_data_len();
+    frag.m_entryOps++;
     break;
   default:
     ndbrequire(false);

=== modified file 'storage/ndb/src/kernel/blocks/dbtux/DbtuxProxy.cpp'
--- a/storage/ndb/src/kernel/blocks/dbtux/DbtuxProxy.cpp	2011-02-02 00:40:07 +0000
+++ b/storage/ndb/src/kernel/blocks/dbtux/DbtuxProxy.cpp	2011-05-25 06:42:30 +0000
@@ -15,6 +15,7 @@
 
 #include "DbtuxProxy.hpp"
 #include "Dbtux.hpp"
+#include "../dblqh/DblqhCommon.hpp"
 
 DbtuxProxy::DbtuxProxy(Block_context& ctx) :
   LocalProxy(DBTUX, ctx)
@@ -23,6 +24,14 @@ DbtuxProxy::DbtuxProxy(Block_context& ct
   addRecSignal(GSN_ALTER_INDX_IMPL_REQ, &DbtuxProxy::execALTER_INDX_IMPL_REQ);
   addRecSignal(GSN_ALTER_INDX_IMPL_CONF, &DbtuxProxy::execALTER_INDX_IMPL_CONF);
   addRecSignal(GSN_ALTER_INDX_IMPL_REF, &DbtuxProxy::execALTER_INDX_IMPL_REF);
+
+  // GSN_INDEX_STAT_IMPL_REQ
+  addRecSignal(GSN_INDEX_STAT_IMPL_REQ, &DbtuxProxy::execINDEX_STAT_IMPL_REQ);
+  addRecSignal(GSN_INDEX_STAT_IMPL_CONF, &DbtuxProxy::execINDEX_STAT_IMPL_CONF);
+  addRecSignal(GSN_INDEX_STAT_IMPL_REF, &DbtuxProxy::execINDEX_STAT_IMPL_REF);
+
+  // GSN_INDEX_STAT_REP
+  addRecSignal(GSN_INDEX_STAT_REP, &DbtuxProxy::execINDEX_STAT_REP);
 }
 
 DbtuxProxy::~DbtuxProxy()
@@ -107,4 +116,176 @@ DbtuxProxy::sendALTER_INDX_IMPL_CONF(Sig
   ssRelease<Ss_ALTER_INDX_IMPL_REQ>(ssId);
 }
 
+// GSN_INDEX_STAT_IMPL_REQ
+
+void
+DbtuxProxy::execINDEX_STAT_IMPL_REQ(Signal* signal)
+{
+  jamEntry();
+  const IndexStatImplReq* req =
+    (const IndexStatImplReq*)signal->getDataPtr();
+  Ss_INDEX_STAT_IMPL_REQ& ss = ssSeize<Ss_INDEX_STAT_IMPL_REQ>();
+  ss.m_req = *req;
+  ndbrequire(signal->getLength() == IndexStatImplReq::SignalLength);
+  sendREQ(signal, ss);
+}
+
+void
+DbtuxProxy::sendINDEX_STAT_IMPL_REQ(Signal* signal, Uint32 ssId,
+                                    SectionHandle*)
+{
+  Ss_INDEX_STAT_IMPL_REQ& ss = ssFind<Ss_INDEX_STAT_IMPL_REQ>(ssId);
+
+  IndexStatImplReq* req = (IndexStatImplReq*)signal->getDataPtrSend();
+  *req = ss.m_req;
+  req->senderRef = reference();
+  req->senderData = ssId;
+
+  const Uint32 instance = workerInstance(ss.m_worker);
+  NdbLogPartInfo lpinfo(instance);
+
+  //XXX remove unused
+  switch (req->requestType) {
+  case IndexStatReq::RT_START_MON:
+    /*
+     * DICT sets fragId if assigned frag is on this node, or else ZNIL
+     * to turn off any possible old assignment.  In MT-LQH we also have
+     * to check which worker owns the frag.
+     */
+    if (req->fragId != ZNIL
+        && !lpinfo.partNoOwner(req->indexId, req->fragId)) {
+      jam();
+      req->fragId = ZNIL;
+    }
+    break;
+  case IndexStatReq::RT_STOP_MON:
+    /*
+     * DICT sets fragId to ZNIL always.  There is no (pointless) check
+     * to see if the frag was ever assigned.
+     */
+    ndbrequire(req->fragId == ZNIL);
+    break;
+  case IndexStatReq::RT_SCAN_FRAG:
+    ndbrequire(req->fragId != ZNIL);
+    if (!lpinfo.partNoOwner(req->indexId, req->fragId)) {
+      jam();
+      skipReq(ss);
+      return;
+    }
+    break;
+  case IndexStatReq::RT_CLEAN_NEW:
+  case IndexStatReq::RT_CLEAN_OLD:
+  case IndexStatReq::RT_CLEAN_ALL:
+    ndbrequire(req->fragId == ZNIL);
+    break;
+  case IndexStatReq::RT_DROP_HEAD:
+    /*
+     * Only one client can do the PK-delete of the head record.  We use
+     * of course the worker which owns the assigned fragment.
+     */
+    ndbrequire(req->fragId != ZNIL);
+    if (!lpinfo.partNoOwner(req->indexId, req->fragId)) {
+      jam();
+      skipReq(ss);
+      return;
+    }
+    break;
+  default:
+    ndbrequire(false);
+    break;
+  }
+
+  sendSignal(workerRef(ss.m_worker), GSN_INDEX_STAT_IMPL_REQ,
+             signal, IndexStatImplReq::SignalLength, JBB);
+}
+
+void
+DbtuxProxy::execINDEX_STAT_IMPL_CONF(Signal* signal)
+{
+  jamEntry();
+  const IndexStatImplConf* conf =
+    (const IndexStatImplConf*)signal->getDataPtr();
+  Uint32 ssId = conf->senderData;
+  Ss_INDEX_STAT_IMPL_REQ& ss = ssFind<Ss_INDEX_STAT_IMPL_REQ>(ssId);
+  recvCONF(signal, ss);
+}
+
+void
+DbtuxProxy::execINDEX_STAT_IMPL_REF(Signal* signal)
+{
+  jamEntry();
+  const IndexStatImplRef* ref = (const IndexStatImplRef*)signal->getDataPtr();
+  Uint32 ssId = ref->senderData;
+  Ss_INDEX_STAT_IMPL_REQ& ss = ssFind<Ss_INDEX_STAT_IMPL_REQ>(ssId);
+  recvREF(signal, ss, ref->errorCode);
+}
+
+void
+DbtuxProxy::sendINDEX_STAT_IMPL_CONF(Signal* signal, Uint32 ssId)
+{
+  Ss_INDEX_STAT_IMPL_REQ& ss = ssFind<Ss_INDEX_STAT_IMPL_REQ>(ssId);
+  BlockReference dictRef = ss.m_req.senderRef;
+
+  if (!lastReply(ss))
+    return;
+
+  if (ss.m_error == 0) {
+    jam();
+    IndexStatImplConf* conf = (IndexStatImplConf*)signal->getDataPtrSend();
+    conf->senderRef = reference();
+    conf->senderData = ss.m_req.senderData;
+    sendSignal(dictRef, GSN_INDEX_STAT_IMPL_CONF,
+               signal, IndexStatImplConf::SignalLength, JBB);
+  } else {
+    IndexStatImplRef* ref = (IndexStatImplRef*)signal->getDataPtrSend();
+    ref->senderRef = reference();
+    ref->senderData = ss.m_req.senderData;
+    ref->errorCode = ss.m_error;
+    sendSignal(dictRef, GSN_INDEX_STAT_IMPL_REF,
+               signal, IndexStatImplRef::SignalLength, JBB);
+  }
+
+  ssRelease<Ss_INDEX_STAT_IMPL_REQ>(ssId);
+}
+
+// GSN_INDEX_STAT_REP
+
+void
+DbtuxProxy::execINDEX_STAT_REP(Signal* signal)
+{
+  jamEntry();
+  const IndexStatRep* rep =
+    (const IndexStatRep*)signal->getDataPtr();
+  Ss_INDEX_STAT_REP& ss = ssSeize<Ss_INDEX_STAT_REP>();
+  ss.m_rep = *rep;
+  ndbrequire(signal->getLength() == IndexStatRep::SignalLength);
+  sendREQ(signal, ss);
+  ssRelease<Ss_INDEX_STAT_REP>(ss);
+}
+
+void
+DbtuxProxy::sendINDEX_STAT_REP(Signal* signal, Uint32 ssId,
+                               SectionHandle*)
+{
+  Ss_INDEX_STAT_REP& ss = ssFind<Ss_INDEX_STAT_REP>(ssId);
+
+  IndexStatRep* rep = (IndexStatRep*)signal->getDataPtrSend();
+  *rep = ss.m_rep;
+  rep->senderData = reference();
+  rep->senderData = ssId;
+
+  const Uint32 instance = workerInstance(ss.m_worker);
+  NdbLogPartInfo lpinfo(instance);
+
+  ndbrequire(rep->fragId != ZNIL);
+  if (!lpinfo.partNoOwner(rep->indexId, rep->fragId)) {
+    jam();
+    skipReq(ss);
+    return;
+  }
+
+  sendSignal(workerRef(ss.m_worker), GSN_INDEX_STAT_REP,
+             signal, IndexStatRep::SignalLength, JBB);
+}
+
 BLOCK_FUNCTIONS(DbtuxProxy)

=== modified file 'storage/ndb/src/kernel/blocks/dbtux/DbtuxProxy.hpp'
--- a/storage/ndb/src/kernel/blocks/dbtux/DbtuxProxy.hpp	2011-02-02 00:40:07 +0000
+++ b/storage/ndb/src/kernel/blocks/dbtux/DbtuxProxy.hpp	2011-05-25 06:42:30 +0000
@@ -19,6 +19,7 @@
 #include <LocalProxy.hpp>
 #include <signaldata/AlterIndxImpl.hpp>
 #include <signaldata/DropTab.hpp>
+#include <signaldata/IndexStatSignal.hpp>
 
 class DbtuxProxy : public LocalProxy {
 public:
@@ -47,6 +48,41 @@ protected:
   void execALTER_INDX_IMPL_CONF(Signal*);
   void execALTER_INDX_IMPL_REF(Signal*);
   void sendALTER_INDX_IMPL_CONF(Signal*, Uint32 ssId);
+
+  // GSN_INDEX_STAT_IMPL_REQ
+  struct Ss_INDEX_STAT_IMPL_REQ : SsParallel {
+    IndexStatImplReq m_req;
+    Ss_INDEX_STAT_IMPL_REQ() {
+      m_sendREQ = (SsFUNCREQ)&DbtuxProxy::sendINDEX_STAT_IMPL_REQ;
+      m_sendCONF = (SsFUNCREP)&DbtuxProxy::sendINDEX_STAT_IMPL_CONF;
+    }
+    enum { poolSize = 1 };
+    static SsPool<Ss_INDEX_STAT_IMPL_REQ>& pool(LocalProxy* proxy) {
+      return ((DbtuxProxy*)proxy)->c_ss_INDEX_STAT_IMPL_REQ;
+    }
+  };
+  SsPool<Ss_INDEX_STAT_IMPL_REQ> c_ss_INDEX_STAT_IMPL_REQ;
+  void execINDEX_STAT_IMPL_REQ(Signal*);
+  void sendINDEX_STAT_IMPL_REQ(Signal*, Uint32 ssId, SectionHandle*);
+  void execINDEX_STAT_IMPL_CONF(Signal*);
+  void execINDEX_STAT_IMPL_REF(Signal*);
+  void sendINDEX_STAT_IMPL_CONF(Signal*, Uint32 ssId);
+
+  // GSN_INDEX_STAT_REP
+  struct Ss_INDEX_STAT_REP : SsParallel {
+    IndexStatRep m_rep;
+    Ss_INDEX_STAT_REP() {
+      m_sendREQ = (SsFUNCREQ)&DbtuxProxy::sendINDEX_STAT_REP;
+      m_sendCONF = 0;
+    }
+    enum { poolSize = 1 };
+    static SsPool<Ss_INDEX_STAT_REP>& pool(LocalProxy* proxy) {
+      return ((DbtuxProxy*)proxy)->c_ss_INDEX_STAT_REP;
+    }
+  };
+  SsPool<Ss_INDEX_STAT_REP> c_ss_INDEX_STAT_REP;
+  void execINDEX_STAT_REP(Signal*);
+  void sendINDEX_STAT_REP(Signal*, Uint32 ssId, SectionHandle*);
 };
 
 #endif

=== modified file 'storage/ndb/src/kernel/blocks/dbtux/DbtuxScan.cpp'
--- a/storage/ndb/src/kernel/blocks/dbtux/DbtuxScan.cpp	2011-05-30 08:50:17 +0000
+++ b/storage/ndb/src/kernel/blocks/dbtux/DbtuxScan.cpp	2011-05-31 08:28:58 +0000
@@ -95,6 +95,30 @@ Dbtux::execACC_SCANREQ(Signal* signal)
      * 0 0 0 - read latest (read lock)
      * 0 1 1 - read exclusive (write lock)
      */
+    const bool isStatScan = AccScanReq::getStatScanFlag(req->requestInfo);
+    if (unlikely(isStatScan)) {
+      jam();
+      if (!scanPtr.p->m_readCommitted) {
+        jam();
+        errorCode = AccScanRef::TuxInvalidLockMode;
+        break;
+      }
+      StatOpPtr statPtr;
+      if (!c_statOpPool.seize(statPtr)) {
+        jam();
+        errorCode = AccScanRef::TuxNoFreeStatOp;
+        break;
+      }
+      scanPtr.p->m_statOpPtrI = statPtr.i;
+      new (statPtr.p) StatOp(*indexPtr.p);
+      statPtr.p->m_scanOpPtrI = scanPtr.i;
+      // rest of StatOp is initialized in execTUX_BOUND_INFO
+#ifdef VM_TRACE
+      if (debugFlags & DebugStat) {
+        debugOut << "Seize stat op" << endl;
+      }
+#endif
+    }
 #ifdef VM_TRACE
     if (debugFlags & DebugScan) {
       debugOut << "Seize scan " << scanPtr.i << " " << *scanPtr.p << endl;
@@ -144,10 +168,34 @@ Dbtux::execTUX_BOUND_INFO(Signal* signal
   jamEntry();
   // get records
   TuxBoundInfo* const req = (TuxBoundInfo*)signal->getDataPtrSend();
-  ScanOp& scan = *c_scanOpPool.getPtr(req->tuxScanPtrI);
+  ScanOpPtr scanPtr;
+  scanPtr.i = req->tuxScanPtrI;
+  c_scanOpPool.getPtr(scanPtr);
+  ScanOp& scan = *scanPtr.p;
   const Index& index = *c_indexPool.getPtr(scan.m_indexId);
-  //UNUSED const DescHead& descHead = getDescHead(index);
-  //UNUSED const KeyType* keyTypes = getKeyTypes(descHead);
+  const DescHead& descHead = getDescHead(index);
+  const KeyType* keyTypes = getKeyTypes(descHead);
+  // data passed in Signal
+  const Uint32* const boundData = &req->data[0];
+  Uint32 boundLen = req->boundAiLength;
+  Uint32 boundOffset = 0;
+  // initialize stats scan
+  if (unlikely(scan.m_statOpPtrI != RNIL)) {
+    // stats options before bounds
+    StatOpPtr statPtr;
+    statPtr.i = scan.m_statOpPtrI;
+    c_statOpPool.getPtr(statPtr);
+    Uint32 usedLen = 0;
+    if (statScanInit(statPtr, boundData, boundLen, &usedLen) == -1) {
+      jam();
+      ndbrequire(scan.m_errorCode != 0);
+      req->errorCode = scan.m_errorCode;
+      return;
+    }
+    ndbrequire(usedLen <= boundLen);
+    boundLen -= usedLen;
+    boundOffset += usedLen;
+  }
   // extract lower and upper bound in separate passes
   for (unsigned idir = 0; idir <= 1; idir++) {
     jam();
@@ -159,9 +207,9 @@ Dbtux::execTUX_BOUND_INFO(Signal* signal
     BoundInfo boundInfo[MaxIndexAttributes];
     // largest attrId seen plus one
     Uint32 maxAttrId = 0;
-    const Uint32* const data = &req->data[0];
+    const Uint32* const data = &boundData[boundOffset];
     Uint32 offset = 0;
-    while (offset + 2 <= req->boundAiLength) {
+    while (offset + 2 <= boundLen) {
       jam();
       const Uint32 type = data[offset];
       const AttributeHeader* ah = (const AttributeHeader*)&data[offset + 1];
@@ -210,7 +258,7 @@ Dbtux::execTUX_BOUND_INFO(Signal* signal
       // jump to next
       offset += 2 + dataSize;
     }
-    if (unlikely(offset != req->boundAiLength)) {
+    if (unlikely(offset != boundLen)) {
       jam();
       scan.m_errorCode = TuxBoundInfo::InvalidAttrInfo;
       req->errorCode = scan.m_errorCode;
@@ -798,7 +846,20 @@ Dbtux::scanFind(ScanOpPtr scanPtr)
       NodeHandle node(frag);
       selectNode(node, pos.m_loc);
       const TreeEnt ent = node.getEnt(pos.m_pos);
-      if (scanVisible(scanPtr, ent)) {
+      if (unlikely(scan.m_statOpPtrI != RNIL)) {
+        StatOpPtr statPtr;
+        statPtr.i = scan.m_statOpPtrI;
+        c_statOpPool.getPtr(statPtr);
+        // report row to stats, returns true if a sample is available
+        int ret = statScanAddRow(statPtr, ent);
+        if (ret == 1) {
+          jam();
+          scan.m_state = ScanOp::Found;
+          // may not access non-pseudo cols but must return valid ent
+          scan.m_scanEnt = ent;
+          break;
+        }
+      } else if (scanVisible(scanPtr, ent)) {
         jam();
         scan.m_state = ScanOp::Found;
         scan.m_scanEnt = ent;
@@ -1205,6 +1266,13 @@ Dbtux::releaseScanOp(ScanOpPtr& scanPtr)
     LocalDataBuffer<ScanBoundSegmentSize> b(c_scanBoundPool, head);
     b.release();
   }
+  if (unlikely(scanPtr.p->m_statOpPtrI != RNIL)) {
+    jam();
+    StatOpPtr statPtr;
+    statPtr.i = scanPtr.p->m_statOpPtrI;
+    c_statOpPool.getPtr(statPtr);
+    c_statOpPool.release(statPtr);
+  }
   // unlink from per-fragment list and release from pool
   frag.m_scanList.release(scanPtr);
 }

=== modified file 'storage/ndb/src/kernel/blocks/dbtux/DbtuxStat.cpp'
--- a/storage/ndb/src/kernel/blocks/dbtux/DbtuxStat.cpp	2011-05-04 12:08:38 +0000
+++ b/storage/ndb/src/kernel/blocks/dbtux/DbtuxStat.cpp	2011-05-24 17:54:07 +0000
@@ -18,6 +18,22 @@
 
 #define DBTUX_STAT_CPP
 #include "Dbtux.hpp"
+#include <math.h>
+
+// debug note: uses new-style debug macro "D" unlike rest of DBTUX
+// there is no filtering feature (yet) like "DebugStat"
+
+#ifdef VM_TRACE
+inline NdbOut&
+NdbOut::operator<<(double x)
+{
+  NdbOut& out = *this;
+  char buf[100];
+  snprintf(buf, sizeof(buf), "%.02f", x);
+  out << buf;
+  return out;
+}
+#endif
 
 void
 Dbtux::execREAD_PSEUDO_REQ(Signal* signal)
@@ -26,14 +42,38 @@ Dbtux::execREAD_PSEUDO_REQ(Signal* signa
   ScanOpPtr scanPtr;
   scanPtr.i = signal->theData[0];
   c_scanOpPool.getPtr(scanPtr);
-  if (signal->theData[1] == AttributeHeader::RECORDS_IN_RANGE) {
+  StatOpPtr statPtr;
+  statPtr.i = scanPtr.p->m_statOpPtrI;
+
+  Uint32 attrId = signal->theData[1];
+  Uint32* out = &signal->theData[0];
+
+  switch (attrId) {
+  case AttributeHeader::RECORDS_IN_RANGE:
     jam();
-    statRecordsInRange(scanPtr, &signal->theData[0]);
-  } else {
-    ndbassert(false);
+    ndbrequire(statPtr.i == RNIL);
+    statRecordsInRange(scanPtr, out);
+    break;
+  case AttributeHeader::INDEX_STAT_KEY:
+    jam();
+    ndbrequire(statPtr.i != RNIL);
+    c_statOpPool.getPtr(statPtr);
+    statScanReadKey(statPtr, out);
+    break;
+  case AttributeHeader::INDEX_STAT_VALUE:
+    jam();
+    ndbrequire(statPtr.i != RNIL);
+    c_statOpPool.getPtr(statPtr);
+    statScanReadValue(statPtr, out);
+    break;
+  default:
+    ndbrequire(false);
+    break;
   }
 }
 
+// RECORDS_IN_RANGE
+
 /*
  * Estimate entries in range.  Scan is at first entry.  Search for last
  * entry i.e. start of descending scan.  Use the 2 positions to estimate
@@ -64,7 +104,8 @@ Dbtux::statRecordsInRange(ScanOpPtr scan
     // committed read (same timeslice) and range not empty
     ndbrequire(pos2.m_loc != NullTupLoc);
   }
-  out[0] = frag.m_tree.m_entryCount;
+  // wl4124_todo change all to Uint64 if ever needed (unlikely)
+  out[0] = (Uint32)frag.m_entryCount;
   out[2] = getEntriesBeforeOrAfter(frag, pos1, 0);
   out[3] = getEntriesBeforeOrAfter(frag, pos2, 1);
   if (pos1.m_loc == pos2.m_loc) {
@@ -97,7 +138,7 @@ Dbtux::getEntriesBeforeOrAfter(Frag& fra
   ndbrequire(depth != 0 && depth <= MaxTreeDepth);
   TreeHead& tree = frag.m_tree;
   Uint32 cnt = 0;
-  Uint32 tot = tree.m_entryCount;
+  Uint32 tot = (Uint32)frag.m_entryCount;
   unsigned i = 0;
   // contribution from levels above
   while (i + 1 < depth) {
@@ -155,3 +196,491 @@ Dbtux::getPathToNode(NodeHandle node, Ui
   path[j] = 0xFFFF; // catch bug
   return depth;
 }
+
+// stats scan
+
+// windows has no log2
+static double
+tux_log2(double x)
+{
+  return ::log(x) / ::log((double)2.0);
+}
+
+int
+Dbtux::statScanInit(StatOpPtr statPtr, const Uint32* data, Uint32 len,
+                    Uint32* usedLen)
+{
+  StatOp& stat = *statPtr.p;
+  ScanOp& scan = *c_scanOpPool.getPtr(stat.m_scanOpPtrI);
+  Frag& frag = *c_fragPool.getPtr(scan.m_fragPtrI);
+  const Index& index = *c_indexPool.getPtr(scan.m_indexId);
+  D("statScanInit");
+
+  // options
+  stat.m_saveSize = c_indexStatSaveSize;
+  stat.m_saveScale = c_indexStatSaveScale;
+  Uint32 offset = 0;
+  while (offset + 1 <= len)
+  {
+    const Uint32 type = data[offset];
+    const Uint32 value = data[offset + 1];
+    switch (type) {
+    case TuxBoundInfo::StatSaveSize:
+      jam();
+      stat.m_saveSize = value;
+      break;
+    case TuxBoundInfo::StatSaveScale:
+      jam();
+      stat.m_saveScale = value;
+      break;
+    default:
+      jam();
+      scan.m_errorCode = TuxBoundInfo::InvalidBounds;
+      return -1;
+    }
+    offset += 2;
+  }
+  *usedLen = offset;
+
+  // average key bytes as stored in stats
+  Uint32 avgKeyBytes = 0;
+  if (frag.m_entryCount != 0)
+  {
+    avgKeyBytes = (Uint32)(frag.m_entryBytes / frag.m_entryCount);
+    if (avgKeyBytes > stat.m_keySpec.get_max_data_len(false))
+      avgKeyBytes = stat.m_keySpec.get_max_data_len(false);
+  }
+
+  // compute batch size - see wl4124.txt
+  {
+    double a = stat.m_saveSize;
+    double b = stat.m_saveScale;
+    double c = avgKeyBytes;
+    double d = index.m_numAttrs;
+    double e =  c + (1 + d) * 4; // approx size of one sample
+    double f = (double)frag.m_entryCount;
+    double g = f * e; // max possible sample bytes
+    if (g < 1.0)
+      g = 1.0;
+    double h = 1 + 0.01 * b * tux_log2(g); // scale factor
+    double i = a * h; // sample bytes allowed
+    double j = i / e; // sample count
+    double k = f / j; // sampling frequency
+    if (k < 1.0)
+      k = 1.0;
+    double l = e * f / k; // estimated sample bytes
+
+    stat.m_batchSize = (Uint32)(k + 0.5);
+    stat.m_estBytes = (Uint32)(l + 0.5);
+    ndbrequire(stat.m_batchSize != 0);
+    D("computed batch size" << V(stat));
+  }
+
+  // key spec is already defined as ref to index key spec
+  stat.m_keyCount = index.m_numAttrs;
+  stat.m_valueCount = 1 + stat.m_keyCount;
+  stat.m_keyData1.reset();
+  stat.m_keyData2.reset();
+
+  // define value spec
+  stat.m_valueCount = 1 + stat.m_keyCount;
+  NdbPack::Spec& valueSpec = stat.m_valueSpec;
+  valueSpec.reset();
+  {
+    NdbPack::Type type(NDB_TYPE_UNSIGNED, 4, false, 0);
+    int ret = valueSpec.add(type, stat.m_valueCount);
+    ndbrequire(ret == 0);
+  }
+
+  return 0;
+}
+
+int
+Dbtux::statScanAddRow(StatOpPtr statPtr, TreeEnt ent)
+{
+  StatOp& stat = *statPtr.p;
+  ScanOp& scan = *c_scanOpPool.getPtr(stat.m_scanOpPtrI);
+  Frag& frag = *c_fragPool.getPtr(scan.m_fragPtrI);
+  D("statScanAddRow" << V(stat));
+
+  KeyData& keyData1 = stat.m_keyData1;
+  KeyData& keyData2 = stat.m_keyData2;
+  StatOp::Value& value1 = stat.m_value1;
+  StatOp::Value& value2 = stat.m_value2;
+
+  stat.m_rowCount++;
+  stat.m_batchCurr++;
+  const bool firstRow = (stat.m_rowCount == 1);
+  int ret;
+
+  // save previous value
+  if (!firstRow) {
+    ret = keyData1.copy(keyData2);
+    ndbrequire(ret == 0);
+    value1 = value2;
+  }
+
+  // read current entry key
+  readKeyAttrs(c_ctx, frag, ent, keyData2, stat.m_keyCount);
+
+  // calculate new values
+  value2.m_rir = stat.m_rowCount;
+  if (firstRow)
+  {
+    for (Uint32 i = 0; i < stat.m_keyCount; i++)
+      value2.m_unq[i] = 1;
+    stat.m_keyChange = false;
+  }
+  else
+  {
+    // how many initial attrs are equal
+    Uint32 num_eq;
+    int res = keyData1.cmp(keyData2, stat.m_keyCount, num_eq);
+    ndbrequire(res <= 0);
+    stat.m_keyChange = (res != 0);
+
+    if (stat.m_keyChange)
+    {
+      ndbrequire(num_eq < stat.m_keyCount);
+      value2.m_unq[num_eq]++;
+      // propagate down
+      for (Uint32 i = num_eq + 1; i < stat.m_keyCount; i++)
+        value2.m_unq[i]++;
+    }
+  }
+
+  // always report last index entry
+  bool lastEntry = false;
+  do
+  {
+    NodeHandle node(frag);
+    TreePos pos = scan.m_scanPos;
+    selectNode(node, pos.m_loc);
+    // more entries in this node
+    const unsigned occup = node.getOccup();
+    // funny cast to avoid signed vs unsigned warning
+    if (pos.m_dir == 3 && pos.m_pos + (unsigned)1 < occup)
+    {
+      jam();
+      break;
+    }
+    // can continue to right sub-tree
+    if (node.getLink(1) != NullTupLoc)
+    {
+      jam();
+      break;
+    }
+    // while child on right
+    while (node.getSide() == 1)
+    {
+      jam();
+      TupLoc loc = node.getLink(2);
+      selectNode(node, loc);
+    }
+    // did not reach root
+    if (node.getSide() != 2)
+    {
+      jam();
+      break;
+    }
+    lastEntry = true;
+  }
+  while (0);
+
+  stat.m_usePrev = true;
+  if (lastEntry)
+  {
+    jam();
+    stat.m_usePrev = false;
+    return 1;
+  }
+  if (stat.m_batchCurr >= stat.m_batchSize && stat.m_keyChange)
+  {
+    jam();
+    stat.m_batchCurr = 0;
+    return 1;
+  }
+  return 0;
+}
+
+void
+Dbtux::statScanReadKey(StatOpPtr statPtr, Uint32* out)
+{
+  StatOp& stat = *statPtr.p;
+  int ret;
+
+  KeyData& keyData = stat.m_keyData;
+  ret = keyData.copy(stat.m_usePrev ? stat.m_keyData1 : stat.m_keyData2);
+  ndbrequire(ret == 0);
+  D("statScanReadKey" << V(keyData));
+  keyData.convert(NdbPack::Endian::Little);
+  memcpy(out, keyData.get_full_buf(), keyData.get_full_len());
+}
+
+void
+Dbtux::statScanReadValue(StatOpPtr statPtr, Uint32* out)
+{
+  StatOp& stat = *statPtr.p;
+  int ret;
+  Uint32 len_out;
+
+  const StatOp::Value& value = stat.m_usePrev ? stat.m_value1 : stat.m_value2;
+
+  // verify sanity
+  ndbrequire(value.m_rir != 0);
+  for (Uint32 k = 0; k < stat.m_keyCount; k++)
+  {
+    ndbrequire(value.m_unq[k] != 0);
+    ndbrequire(value.m_rir >= value.m_unq[k]);
+    ndbrequire(k == 0 || value.m_unq[k] >= value.m_unq[k - 1]);
+  }
+
+  NdbPack::Data& valueData = stat.m_valueData;
+  valueData.reset();
+
+  ret = valueData.add(&value.m_rir, &len_out);
+  ndbrequire(ret == 0 && len_out == 4);
+  ret = valueData.add(&value.m_unq[0], stat.m_keyCount, &len_out);
+  ndbrequire(ret == 0 && len_out == stat.m_keyCount * 4);
+  ret = valueData.finalize();
+  ndbrequire(ret == 0);
+
+  D("statScanReadValue" << V(valueData));
+  valueData.convert(NdbPack::Endian::Little);
+  memcpy(out, valueData.get_full_buf(), valueData.get_full_len());
+}
+
+// at end of stats update, TRIX sends loadTime
+void
+Dbtux::execINDEX_STAT_REP(Signal* signal)
+{
+  jamEntry();
+  const IndexStatRep* rep = (const IndexStatRep*)signal->getDataPtr();
+
+  switch (rep->requestType) {
+  case IndexStatRep::RT_UPDATE_REQ:
+    ndbrequire(false);
+    break;
+  case IndexStatRep::RT_UPDATE_CONF:
+    {
+      Index& index = *c_indexPool.getPtr(rep->indexId);
+      FragPtr fragPtr;
+      findFrag(index, rep->fragId, fragPtr);
+      ndbrequire(fragPtr.i != RNIL);
+      // index.m_statFragPtrI need not be defined yet
+      D("loadTime" << V(index.m_statLoadTime) << " ->" << V(rep->loadTime));
+      index.m_statLoadTime = rep->loadTime;
+    }
+    break;
+  default:
+    ndbrequire(false);
+    break;
+  }
+}
+
+// stats monitor
+
+void
+Dbtux::execINDEX_STAT_IMPL_REQ(Signal* signal)
+{
+  jamEntry();
+  const IndexStatImplReq* req = (const IndexStatImplReq*)signal->getDataPtr();
+
+  StatMon& mon = c_statMon;
+  mon.m_req = *req;
+  mon.m_requestType = req->requestType;
+
+  switch (mon.m_requestType) {
+  case IndexStatReq::RT_START_MON:
+    statMonStart(signal, mon);
+    break;
+  case IndexStatReq::RT_STOP_MON:
+    statMonStop(signal, mon);
+    break;
+  default:
+    ndbrequire(false);
+    break;
+  }
+}
+
+void
+Dbtux::statMonStart(Signal* signal, StatMon& mon)
+{
+  const IndexStatImplReq* req = &mon.m_req;
+  Index& index = *c_indexPool.getPtr(req->indexId);
+  D("statMonStart" << V(mon));
+
+  // RT_START_MON also sends ZNIL to all non-monitoring nodes
+  if (req->fragId == ZNIL)
+  {
+    jam();
+    index.m_statFragPtrI = RNIL;
+    D("non-monitoring node");
+  }
+  else
+  {
+    jam();
+    FragPtr fragPtr;
+    findFrag(index, req->fragId, fragPtr);
+    ndbrequire(fragPtr.i != RNIL);
+    index.m_statFragPtrI = fragPtr.i;
+    fragPtr.p->m_entryOps = 0;
+    D("monitoring node" << V(index));
+  }
+
+  statMonConf(signal, mon);
+}
+
+void
+Dbtux::statMonStop(Signal* signal, StatMon& mon)
+{
+  const IndexStatImplReq* req = &mon.m_req;
+  Index& index = *c_indexPool.getPtr(req->indexId);
+  D("statMonStop" << V(mon));
+
+  // RT_STOP_MON simply sends ZNIL to every node
+  ndbrequire(req->fragId == ZNIL);
+  index.m_statFragPtrI = RNIL;
+
+  statMonConf(signal, mon);
+}
+
+void
+Dbtux::statMonConf(Signal* signal, StatMon& mon)
+{
+  const IndexStatImplReq* req = &mon.m_req;
+  D("statMonConf" << V(mon));
+
+  IndexStatImplConf* conf = (IndexStatImplConf*)signal->getDataPtrSend();
+  conf->senderRef = reference();
+  conf->senderData = req->senderData;
+  sendSignal(req->senderRef, GSN_INDEX_STAT_IMPL_CONF,
+             signal, IndexStatImplConf::SignalLength, JBB);
+}
+
+// continueB loop
+
+void
+Dbtux::statMonSendContinueB(Signal* signal)
+{
+  StatMon& mon = c_statMon;
+  D("statMonSendContinueB" << V(mon));
+
+  signal->theData[0] = TuxContinueB::StatMon;
+  signal->theData[1] = mon.m_loopIndexId;
+  sendSignalWithDelay(reference(), GSN_CONTINUEB,
+                      signal, mon.m_loopDelay, 2);
+}
+
+void
+Dbtux::statMonExecContinueB(Signal* signal)
+{
+  StatMon& mon = c_statMon;
+  D("statMonExecContinueB" << V(mon));
+
+  if (!c_indexStatAutoUpdate ||
+      c_indexStatTriggerPct == 0 ||
+      getNodeState().startLevel != NodeState::SL_STARTED)
+  {
+  }
+  else
+  {
+    jam();
+    statMonCheck(signal, mon);
+  }
+  statMonSendContinueB(signal);
+}
+
+void
+Dbtux::statMonCheck(Signal* signal, StatMon& mon)
+{
+  const Uint32 now = (Uint32)time(0);
+  D("statMonCheck" << V(mon) << V(now));
+
+  const uint maxloop = 32;
+  for (uint loop = 0; loop < maxloop; loop++, mon.m_loopIndexId++)
+  {
+    jam();
+    mon.m_loopIndexId %= c_indexPool.getSize();
+
+    const Index& index = *c_indexPool.getPtr(mon.m_loopIndexId);
+    if (index.m_state == Index::NotDefined ||
+        index.m_state == Index::Dropping ||
+        index.m_statFragPtrI == RNIL)
+    {
+      jam();
+      continue;
+    }
+    const Frag& frag = *c_fragPool.getPtr(index.m_statFragPtrI);
+
+    bool update = false;
+    if (index.m_statLoadTime == 0)
+    {
+      jam();
+      update = true;
+      D("statMonCheck" << V(update) << V(index.m_statLoadTime));
+    }
+    else if (now < index.m_statLoadTime + c_indexStatUpdateDelay)
+    {
+      jam();
+      update = false;
+      D("statMonCheck" << V(update) << V(index.m_statLoadTime));
+    }
+    else
+    {
+      const Uint64 count = frag.m_entryCount;
+      const Uint64 ops = frag.m_entryOps;
+      if (count <= 1)
+      {
+        jam();
+        update = (ops >= 1);
+        D("statMonCheck" << V(update) << V(ops));
+      }
+      else
+      {
+        jam();
+        // compute scaled percentags - see wl4124.txt
+        double a = c_indexStatTriggerPct;
+        double b = c_indexStatTriggerScale;
+        double c = (double)count;
+        double d = 1 + 0.01 * b * tux_log2(c); // inverse scale factor
+        double e = a / d; // scaled trigger pct
+        double f = (double)ops;
+        double g = 100.0 * f / c;
+        update = (g >= e);
+        D("statMonCheck" << V(update) << V(f) << V(c));
+      }
+    }
+
+    if (update)
+    {
+      jam();
+      statMonRep(signal, mon);
+      // advance index afterwards
+      mon.m_loopIndexId++;
+      break;
+    }
+  }
+}
+
+void
+Dbtux::statMonRep(Signal* signal, StatMon& mon)
+{
+  const Index& index = *c_indexPool.getPtr(mon.m_loopIndexId);
+  const Frag& frag = *c_fragPool.getPtr(index.m_statFragPtrI);
+  D("statMonRep" << V(mon));
+
+  IndexStatRep* rep = (IndexStatRep*)signal->getDataPtrSend();
+  rep->senderRef = reference();
+  rep->senderData = mon.m_loopIndexId;
+  rep->requestType = IndexStatRep::RT_UPDATE_REQ;
+  rep->requestFlag = 0;
+  rep->indexId = mon.m_loopIndexId;
+  rep->indexVersion = 0; // not required
+  rep->tableId = index.m_tableId;
+  rep->fragId = frag.m_fragId;
+  rep->loadTime = index.m_statLoadTime;
+
+  sendSignal(DBDICT_REF, GSN_INDEX_STAT_REP,
+             signal, IndexStatRep::SignalLength, JBB);
+}

=== modified file 'storage/ndb/src/kernel/blocks/dbtux/DbtuxTree.cpp'
--- a/storage/ndb/src/kernel/blocks/dbtux/DbtuxTree.cpp	2011-02-01 23:27:25 +0000
+++ b/storage/ndb/src/kernel/blocks/dbtux/DbtuxTree.cpp	2011-05-19 09:40:36 +0000
@@ -49,7 +49,6 @@ Dbtux::treeAdd(TuxCtx& ctx, Frag& frag,
     tree.m_root = node.m_loc;
     break;
   } while (0);
-  tree.m_entryCount++;
 }
 
 /*
@@ -212,8 +211,6 @@ Dbtux::treeRemove(Frag& frag, TreePos tr
     treeRemoveLeaf(frag, node);
     break;
   } while (0);
-  ndbrequire(tree.m_entryCount != 0);
-  tree.m_entryCount--;
 }
 
 /*

=== modified file 'storage/ndb/src/kernel/blocks/suma/Suma.cpp'
--- a/storage/ndb/src/kernel/blocks/suma/Suma.cpp	2011-05-30 08:50:17 +0000
+++ b/storage/ndb/src/kernel/blocks/suma/Suma.cpp	2011-05-31 08:28:58 +0000
@@ -174,7 +174,12 @@ Suma::execREAD_CONFIG_REQ(Signal* signal
     c_subOpPool.setSize(256);
   
   c_syncPool.setSize(2);
-  c_dataBufferPool.setSize(noAttrs);
+
+  // Trix: max 5 concurrent index stats ops with max 9 words bounds
+  Uint32 noOfBoundWords = 5 * 9;
+
+  // XXX multiplies number of words by 15 ???
+  c_dataBufferPool.setSize(noAttrs + noOfBoundWords);
 
   c_maxBufferedEpochs = maxBufferedEpochs;
 
@@ -2292,6 +2297,7 @@ Suma::execSUB_SYNC_REQ(Signal* signal)
   syncPtr.p->m_error            = 0;
   syncPtr.p->m_requestInfo      = req->requestInfo;
   syncPtr.p->m_frag_cnt         = req->fragCount;
+  syncPtr.p->m_frag_id          = req->fragId;
   syncPtr.p->m_tableId          = subPtr.p->m_tableId;
 
   {
@@ -2302,8 +2308,17 @@ Suma::execSUB_SYNC_REQ(Signal* signal)
       handle.getSection(ptr, SubSyncReq::ATTRIBUTE_LIST);
       LocalDataBuffer<15> attrBuf(c_dataBufferPool, syncPtr.p->m_attributeList);
       append(attrBuf, ptr, getSectionSegmentPool());
-      releaseSections(handle);
     }
+    if (req->requestInfo & SubSyncReq::RangeScan)
+    {
+      jam();
+      ndbrequire(handle.m_cnt > 1)
+      SegmentedSectionPtr ptr;
+      handle.getSection(ptr, SubSyncReq::TUX_BOUND_INFO);
+      LocalDataBuffer<15> boundBuf(c_dataBufferPool, syncPtr.p->m_boundInfo);
+      append(boundBuf, ptr, getSectionSegmentPool());
+    }
+    releaseSections(handle);
   }
 
   /**
@@ -2432,8 +2447,30 @@ Suma::execDIH_SCAN_GET_NODES_CONF(Signal
     fd.m_fragDesc.m_nodeId = conf->nodes[0];
     fd.m_fragDesc.m_fragmentNo = fragNo;
     fd.m_fragDesc.m_lqhInstanceKey = conf->instanceKey;
-    signal->theData[2] = fd.m_dummy;
-    fragBuf.append(&signal->theData[2], 1);
+    if (ptr.p->m_frag_id == ZNIL)
+    {
+      signal->theData[2] = fd.m_dummy;
+      fragBuf.append(&signal->theData[2], 1);
+    }
+    else if (ptr.p->m_frag_id == fragNo)
+    {
+      /*
+       * Given fragment must have a replica on this node.
+       */
+      const Uint32 ownNodeId = getOwnNodeId();
+      Uint32 i = 0;
+      for (i = 0; i < nodeCount; i++)
+        if (conf->nodes[i] == ownNodeId)
+          break;
+      if (i == nodeCount)
+      {
+        sendSubSyncRef(signal, 1428);
+        return;
+      }
+      fd.m_fragDesc.m_nodeId = ownNodeId;
+      signal->theData[2] = fd.m_dummy;
+      fragBuf.append(&signal->theData[2], 1);
+    }
   }
 
   const Uint32 nextFrag = fragNo + 1;
@@ -2740,6 +2777,21 @@ Suma::SyncRecord::nextScan(Signal* signa
     ScanFragReq::setTupScanFlag(req->requestInfo, 1);
   }
 
+  if (m_requestInfo & SubSyncReq::LM_CommittedRead)
+  {
+    ScanFragReq::setReadCommittedFlag(req->requestInfo, 1);
+  }
+
+  if (m_requestInfo & SubSyncReq::RangeScan)
+  {
+    ScanFragReq::setRangeScanFlag(req->requestInfo, 1);
+  }
+
+  if (m_requestInfo & SubSyncReq::StatScan)
+  {
+    ScanFragReq::setStatScanFlag(req->requestInfo, 1);
+  }
+
   req->fragmentNoKeyLen = fd.m_fragDesc.m_fragmentNo;
   req->schemaVersion = tabPtr.p->m_schemaVersion;
   req->transId1 = 0;
@@ -2763,10 +2815,25 @@ Suma::SyncRecord::nextScan(Signal* signa
     AttributeHeader::init(&attrInfo[pos++], * it.data, 0);
   }
   LinearSectionPtr ptr[3];
+  Uint32 noOfSections;
   ptr[0].p = attrInfo;
   ptr[0].sz = pos;
+  noOfSections = 1;
+  if (m_requestInfo & SubSyncReq::RangeScan)
+  {
+    jam();
+    Uint32 oldpos = pos; // after attrInfo
+    LocalDataBuffer<15> boundBuf(suma.c_dataBufferPool, m_boundInfo);
+    for (boundBuf.first(it); !it.curr.isNull(); boundBuf.next(it))
+    {
+      attrInfo[pos++] = *it.data;
+    }
+    ptr[1].p = &attrInfo[oldpos];
+    ptr[1].sz = pos - oldpos;
+    noOfSections = 2;
+  }
   suma.sendSignal(lqhRef, GSN_SCAN_FRAGREQ, signal, 
-		  ScanFragReq::SignalLength, JBB, ptr, 1);
+		  ScanFragReq::SignalLength, JBB, ptr, noOfSections);
   
   m_currentNoOfAttributes = attrBuf.getSize();        
 
@@ -5305,6 +5372,9 @@ Suma::SyncRecord::release(){
 
   LocalDataBuffer<15> attrBuf(suma.c_dataBufferPool, m_attributeList);
   attrBuf.release();  
+
+  LocalDataBuffer<15> boundBuf(suma.c_dataBufferPool, m_boundInfo);
+  boundBuf.release();  
 }
 
 

=== modified file 'storage/ndb/src/kernel/blocks/suma/Suma.hpp'
--- a/storage/ndb/src/kernel/blocks/suma/Suma.hpp	2011-02-01 23:27:25 +0000
+++ b/storage/ndb/src/kernel/blocks/suma/Suma.hpp	2011-05-19 09:38:03 +0000
@@ -171,6 +171,7 @@ public:
     Uint32 m_requestInfo;
 
     Uint32 m_frag_cnt; // only scan this many fragments...
+    Uint32 m_frag_id;  // only scan this specific fragment...
     Uint32 m_tableId;  // redundant...
 
     /**
@@ -185,6 +186,7 @@ public:
     Uint32 m_currentFragment;       // Index in tabPtr.p->m_fragments
     Uint32 m_currentNoOfAttributes; // No of attributes for current table
     DataBuffer<15>::Head m_attributeList; // Attribute if other than default
+    DataBuffer<15>::Head m_boundInfo;  // For range scan
     
     void startScan(Signal*);
     void nextScan(Signal*);

=== modified file 'storage/ndb/src/kernel/blocks/trix/Trix.cpp'
--- a/storage/ndb/src/kernel/blocks/trix/Trix.cpp	2011-02-01 23:27:25 +0000
+++ b/storage/ndb/src/kernel/blocks/trix/Trix.cpp	2011-05-31 08:28:58 +0000
@@ -108,6 +108,14 @@ Trix::Trix(Block_context& ctx) :
 
   addRecSignal(GSN_WAIT_GCP_REF, &Trix::execWAIT_GCP_REF);
   addRecSignal(GSN_WAIT_GCP_CONF, &Trix::execWAIT_GCP_CONF);
+
+  // index stats
+  addRecSignal(GSN_INDEX_STAT_IMPL_REQ, &Trix::execINDEX_STAT_IMPL_REQ);
+  addRecSignal(GSN_GET_TABINFO_CONF, &Trix::execGET_TABINFO_CONF);
+  addRecSignal(GSN_GET_TABINFOREF, &Trix::execGET_TABINFO_REF);
+
+  // index stats sys tables
+  c_statGetMetaDone = false;
 }
 
 /**
@@ -134,6 +142,7 @@ Trix::execREAD_CONFIG_REQ(Signal* signal
   // Allocate pool sizes
   c_theAttrOrderBufferPool.setSize(100);
   c_theSubscriptionRecPool.setSize(100);
+  c_statOpPool.setSize(5);
 
   DLList<SubscriptionRecord> subscriptions(c_theSubscriptionRecPool);
   SubscriptionRecPtr subptr;
@@ -469,12 +478,14 @@ Trix::execDUMP_STATE_ORD(Signal* signal)
   if (signal->theData[0] == DumpStateOrd::SchemaResourceSnapshot)
   {
     RSS_AP_SNAPSHOT_SAVE(c_theSubscriptionRecPool);
+    RSS_AP_SNAPSHOT_SAVE(c_statOpPool);
     return;
   }
 
   if (signal->theData[0] == DumpStateOrd::SchemaResourceCheckLeak)
   {
     RSS_AP_SNAPSHOT_CHECK(c_theSubscriptionRecPool);
+    RSS_AP_SNAPSHOT_CHECK(c_statOpPool);
     return;
   }
   
@@ -597,6 +608,7 @@ void Trix:: execBUILD_INDX_IMPL_REQ(Sign
   subRec->prepareId = RNIL;
   subRec->requestType = INDEX_BUILD;
   subRec->fragCount = 0;
+  subRec->fragId = ZNIL;
   subRec->m_rows_processed = 0;
   subRec->m_flags = SubscriptionRecord::RF_WAIT_GCP; // Todo make configurable
   subRec->m_gci = 0;
@@ -655,6 +667,11 @@ void Trix::execUTIL_PREPARE_CONF(Signal*
     printf("Trix::execUTIL_PREPARE_CONF: Failed to find subscription data %u\n", subRecPtr.i);
     return;
   }
+  if (subRec->requestType == STAT_UTIL)
+  {
+    statUtilPrepareConf(signal, subRec->m_statPtrI);
+    return;
+  }
   subRecPtr.p = subRec;
   subRec->prepareId = utilPrepareConf->prepareId;
   setupSubscription(signal, subRecPtr);
@@ -672,6 +689,11 @@ void Trix::execUTIL_PREPARE_REF(Signal*
     printf("Trix::execUTIL_PREPARE_REF: Failed to find subscription data %u\n", subRecPtr.i);
     return;
   }
+  if (subRec->requestType == STAT_UTIL)
+  {
+    statUtilPrepareRef(signal, subRec->m_statPtrI);
+    return;
+  }
   subRecPtr.p = subRec;
   subRec->errorCode = (BuildIndxRef::ErrorCode)utilPrepareRef->errorCode;
 
@@ -696,6 +718,11 @@ void Trix::execUTIL_EXECUTE_CONF(Signal*
     printf("rix::execUTIL_EXECUTE_CONF: Failed to find subscription data %u\n", subRecPtr.i);
     return;
   }
+  if (subRec->requestType == STAT_UTIL)
+  {
+    statUtilExecuteConf(signal, subRec->m_statPtrI);
+    return;
+  }
   subRecPtr.p = subRec;
   subRec->expectedConf--;
 
@@ -730,6 +757,11 @@ void Trix::execUTIL_EXECUTE_REF(Signal*
     printf("Trix::execUTIL_EXECUTE_REF: Failed to find subscription data %u\n", subRecPtr.i);
     return;
   }
+  if (subRec->requestType == STAT_UTIL)
+  {
+    statUtilExecuteRef(signal, subRec->m_statPtrI);
+    return;
+  }
   subRecPtr.p = subRec;
   ndbrequire(utilExecuteRef->errorCode == UtilExecuteRef::TCError);
   if(utilExecuteRef->TCErrorCode == CONSTRAINT_VIOLATION)
@@ -890,6 +922,21 @@ void Trix::execSUB_TABLE_DATA(Signal* si
   case REORG_DELETE:
     executeReorgTransaction(signal, subRecPtr, subTableData->takeOver);
     break;
+  case STAT_UTIL:
+    ndbrequire(false);
+    break;
+  case STAT_CLEAN:
+    {
+      StatOp& stat = statOpGetPtr(subRecPtr.p->m_statPtrI);
+      statCleanExecute(signal, stat);
+    }
+    break;
+  case STAT_SCAN:
+    {
+      StatOp& stat = statOpGetPtr(subRecPtr.p->m_statPtrI);
+      statScanExecute(signal, stat);
+    }
+    break;
   }
 
   subRecPtr.p->m_rows_processed++;
@@ -939,8 +986,10 @@ void Trix::startTableScan(Signal* signal
   }
   // Merge index and key column segments
   struct LinearSectionPtr orderPtr[3];
+  Uint32 noOfSections;
   orderPtr[0].p = attributeList;
   orderPtr[0].sz = subRec->attributeOrder.getSize();
+  noOfSections = 1;
 
   SubSyncReq * subSyncReq = (SubSyncReq *)signal->getDataPtrSend();
   subSyncReq->senderRef = reference();
@@ -950,6 +999,7 @@ void Trix::startTableScan(Signal* signal
   subSyncReq->part = SubscriptionData::TableData;
   subSyncReq->requestInfo = 0;
   subSyncReq->fragCount = subRec->fragCount;
+  subSyncReq->fragId = subRec->fragId;
 
   if (subRec->m_flags & SubscriptionRecord::RF_NO_DISK)
   {
@@ -974,6 +1024,27 @@ void Trix::startTableScan(Signal* signal
     subSyncReq->requestInfo |= SubSyncReq::LM_Exclusive;
     subSyncReq->requestInfo |= SubSyncReq::Reorg;
   }
+  else if (subRec->requestType == STAT_CLEAN)
+  {
+    jam();
+    StatOp& stat = statOpGetPtr(subRecPtr.p->m_statPtrI);
+    StatOp::Clean clean = stat.m_clean;
+    orderPtr[1].p = clean.m_bound;
+    orderPtr[1].sz = clean.m_boundSize;
+    noOfSections = 2;
+    subSyncReq->requestInfo |= SubSyncReq::LM_CommittedRead;
+    subSyncReq->requestInfo |= SubSyncReq::RangeScan;
+  }
+  else if (subRec->requestType == STAT_SCAN)
+  {
+    jam();
+    orderPtr[1].p = 0;
+    orderPtr[1].sz = 0;
+    noOfSections = 2;
+    subSyncReq->requestInfo |= SubSyncReq::LM_CommittedRead;
+    subSyncReq->requestInfo |= SubSyncReq::RangeScan;
+    subSyncReq->requestInfo |= SubSyncReq::StatScan;
+  }
   subRecPtr.p->expectedConf = 1;
 
   DBUG_PRINT("info",("i: %u subscriptionId: %u, subscriptionKey: %u",
@@ -981,7 +1052,7 @@ void Trix::startTableScan(Signal* signal
 		     subSyncReq->subscriptionKey));
 
   sendSignal(SUMA_REF, GSN_SUB_SYNC_REQ,
-	     signal, SubSyncReq::SignalLength, JBB, orderPtr, 1);
+	     signal, SubSyncReq::SignalLength, JBB, orderPtr, noOfSections);
 }
 
 void Trix::prepareInsertTransactions(Signal* signal, 
@@ -1325,6 +1396,24 @@ Trix::execUTIL_RELEASE_CONF(Signal* sign
                  BuildIndxRef::SignalLength , JBB);
     }
     break;
+  case STAT_UTIL:
+    ndbrequire(subRecPtr.p->errorCode == BuildIndxRef::NoError);
+    statUtilReleaseConf(signal, subRecPtr.p->m_statPtrI);
+    return;
+  case STAT_CLEAN:
+    {
+      subRecPtr.p->prepareId = RNIL;
+      StatOp& stat = statOpGetPtr(subRecPtr.p->m_statPtrI);
+      statCleanRelease(signal, stat);
+    }
+    return;
+  case STAT_SCAN:
+    {
+      subRecPtr.p->prepareId = RNIL;
+      StatOp& stat = statOpGetPtr(subRecPtr.p->m_statPtrI);
+      statScanRelease(signal, stat);
+    }
+    return;
   }
 
   // Release subscription record
@@ -1394,6 +1483,7 @@ Trix::execCOPY_DATA_IMPL_REQ(Signal* sig
   subRec->pendingSubSyncContinueConf = false;
   subRec->prepareId = req->transId;
   subRec->fragCount = req->srcFragments;
+  subRec->fragId = ZNIL;
   subRec->m_rows_processed = 0;
   subRec->m_flags = SubscriptionRecord::RF_WAIT_GCP; // Todo make configurable
   subRec->m_gci = 0;
@@ -1478,6 +1568,1606 @@ Trix::execCOPY_DATA_IMPL_REQ(Signal* sig
   }
 }
 
+// index stats
+
+Trix::StatOp&
+Trix::statOpGetPtr(Uint32 statPtrI)
+{
+  ndbrequire(statPtrI != RNIL);
+  return *c_statOpPool.getPtr(statPtrI);
+}
+
+bool
+Trix::statOpSeize(Uint32& statPtrI)
+{
+  StatOpPtr statPtr;
+  if (ERROR_INSERTED(18001) ||
+      !c_statOpPool.seize(statPtr))
+  {
+    jam();
+    D("statOpSeize: seize statOp failed");
+    return false;
+  }
+#ifdef VM_TRACE
+  memset(statPtr.p, 0xf3, sizeof(*statPtr.p));
+#endif
+  new (statPtr.p) StatOp;
+  statPtrI = statPtr.i;
+  StatOp& stat = statOpGetPtr(statPtrI);
+  stat.m_ownPtrI = statPtrI;
+
+  SubscriptionRecPtr subRecPtr;
+  if (ERROR_INSERTED(18002) ||
+      !c_theSubscriptions.seize(subRecPtr))
+  {
+    jam();
+    c_statOpPool.release(statPtr);
+    D("statOpSeize: seize subRec failed");
+    return false;
+  }
+  SubscriptionRecord* subRec = subRecPtr.p;
+  subRec->m_statPtrI = stat.m_ownPtrI;
+  stat.m_subRecPtrI = subRecPtr.i;
+
+  D("statOpSeize" << V(statPtrI) << V(subRecPtr.i));
+  return true;
+}
+
+void
+Trix::statOpRelease(StatOp& stat)
+{
+  StatOp::Util& util = stat.m_util;
+  D("statOpRelease" << V(stat));
+
+  if (stat.m_subRecPtrI != RNIL)
+  {
+    jam();
+    SubscriptionRecord* subRec = c_theSubscriptions.getPtr(stat.m_subRecPtrI);
+    ndbrequire(subRec->prepareId == RNIL);
+    subRec->attributeOrder.release();
+    c_theSubscriptions.release(stat.m_subRecPtrI);
+    stat.m_subRecPtrI = RNIL;
+  }
+  ndbrequire(util.m_prepareId == RNIL);
+  c_statOpPool.release(stat.m_ownPtrI);
+}
+
+void
+Trix::execINDEX_STAT_IMPL_REQ(Signal* signal)
+{
+  jamEntry();
+  const IndexStatImplReq* req = (const IndexStatImplReq*)signal->getDataPtr();
+
+  Uint32 statPtrI = RNIL;
+  if (!statOpSeize(statPtrI))
+  {
+    jam();
+    statOpRef(signal, req, IndexStatRef::NoFreeStatOp, __LINE__);
+    return;
+  }
+  StatOp& stat = statOpGetPtr(statPtrI);
+  stat.m_req = *req;
+  stat.m_requestType = req->requestType;
+
+  // set request name for cluster log message
+  switch (stat.m_requestType) {
+  case IndexStatReq::RT_CLEAN_NEW:
+    jam();
+    stat.m_requestName = "clean new";
+    break;
+  case IndexStatReq::RT_CLEAN_OLD:
+    jam();
+    stat.m_requestName = "clean old";
+    break;
+  case IndexStatReq::RT_CLEAN_ALL:
+    jam();
+    stat.m_requestName = "clean all";
+    break;
+  case IndexStatReq::RT_SCAN_FRAG:
+    jam();
+    stat.m_requestName = "scan frag";
+    break;
+  case IndexStatReq::RT_DROP_HEAD:
+    jam();
+    stat.m_requestName = "drop head";
+    break;
+  default:
+    ndbrequire(false);
+    break;
+  }
+
+  SubscriptionRecord* subRec = c_theSubscriptions.getPtr(stat.m_subRecPtrI);
+  subRec->prepareId = RNIL;
+  subRec->errorCode = BuildIndxRef::NoError;
+
+  // sys tables are not recreated so do this only once
+  if (!c_statGetMetaDone)
+  {
+    jam();
+    statMetaGetHead(signal, stat);
+    return;
+  }
+  statGetMetaDone(signal, stat);
+}
+
+// sys tables metadata
+
+const Trix::SysColumn
+Trix::g_statMetaHead_column[] = {
+  { 0, "INDEX_ID",
+    true
+  },
+  { 1, "INDEX_VERSION",
+    true
+  },
+  { 2, "TABLE_ID",
+    false
+  },
+  { 3, "FRAG_COUNT",
+    false
+  },
+  { 4, "VALUE_FORMAT",
+    false
+  },
+  { 5, "SAMPLE_VERSION",
+    false
+  },
+  { 6, "LOAD_TIME",
+    false
+  },
+  { 7, "SAMPLE_COUNT",
+    false
+  },
+  { 8, "KEY_BYTES",
+    false
+  }
+};
+
+const Trix::SysColumn
+Trix::g_statMetaSample_column[] = {
+  { 0, "INDEX_ID",
+    true
+  },
+  { 1, "INDEX_VERSION",
+    true
+  },
+  { 2, "SAMPLE_VERSION",
+    true
+  },
+  { 3, "STAT_KEY",
+    true
+  },
+  { 4, "STAT_VALUE",
+    false
+  }
+};
+
+const Trix::SysTable
+Trix::g_statMetaHead = {
+  NDB_INDEX_STAT_DB "/" NDB_INDEX_STAT_SCHEMA "/" NDB_INDEX_STAT_HEAD_TABLE,
+  ~(Uint32)0,
+  sizeof(g_statMetaHead_column)/sizeof(g_statMetaHead_column[0]),
+  g_statMetaHead_column
+};
+
+const Trix::SysTable
+Trix::g_statMetaSample = {
+  NDB_INDEX_STAT_DB "/" NDB_INDEX_STAT_SCHEMA "/" NDB_INDEX_STAT_SAMPLE_TABLE,
+  ~(Uint32)0,
+  sizeof(g_statMetaSample_column)/sizeof(g_statMetaSample_column[0]),
+  g_statMetaSample_column
+};
+
+const Trix::SysIndex
+Trix::g_statMetaSampleX1 = {
+  NDB_INDEX_STAT_DB "/" NDB_INDEX_STAT_SCHEMA "/%u/" NDB_INDEX_STAT_SAMPLE_INDEX1,
+  ~(Uint32)0,
+  ~(Uint32)0
+};
+
+void
+Trix::statMetaGetHead(Signal* signal, StatOp& stat)
+{
+  D("statMetaGetHead" << V(stat));
+  StatOp::Meta& meta = stat.m_meta;
+  meta.m_cb.m_callbackFunction = safe_cast(&Trix::statMetaGetHeadCB);
+  meta.m_cb.m_callbackData = stat.m_ownPtrI;
+  const char* name = g_statMetaHead.name;
+  sendGetTabInfoReq(signal, stat, name);
+}
+
+void
+Trix::statMetaGetHeadCB(Signal* signal, Uint32 statPtrI, Uint32 ret)
+{
+  StatOp& stat = statOpGetPtr(statPtrI);
+  D("statMetaGetHeadCB" << V(stat) << V(ret));
+  StatOp::Meta& meta = stat.m_meta;
+  if (ret != 0)
+  {
+    jam();
+    statOpError(signal, stat, ret, __LINE__);
+    return;
+  }
+  g_statMetaHead.tableId = meta.m_conf.tableId;
+  statMetaGetSample(signal, stat);
+}
+
+void
+Trix::statMetaGetSample(Signal* signal, StatOp& stat)
+{
+  D("statMetaGetSample" << V(stat));
+  StatOp::Meta& meta = stat.m_meta;
+  meta.m_cb.m_callbackFunction = safe_cast(&Trix::statMetaGetSampleCB);
+  meta.m_cb.m_callbackData = stat.m_ownPtrI;
+  const char* name = g_statMetaSample.name;
+  sendGetTabInfoReq(signal, stat, name);
+}
+
+void
+Trix::statMetaGetSampleCB(Signal* signal, Uint32 statPtrI, Uint32 ret)
+{
+  StatOp& stat = statOpGetPtr(statPtrI);
+  D("statMetaGetSampleCB" << V(stat) << V(ret));
+  StatOp::Meta& meta = stat.m_meta;
+  if (ret != 0)
+  {
+    jam();
+    statOpError(signal, stat, ret, __LINE__);
+    return;
+  }
+  g_statMetaSample.tableId = meta.m_conf.tableId;
+  statMetaGetSampleX1(signal, stat);
+}
+
+void
+Trix::statMetaGetSampleX1(Signal* signal, StatOp& stat)
+{
+  D("statMetaGetSampleX1" << V(stat));
+  StatOp::Meta& meta = stat.m_meta;
+  meta.m_cb.m_callbackFunction = safe_cast(&Trix::statMetaGetSampleX1CB);
+  meta.m_cb.m_callbackData = stat.m_ownPtrI;
+  const char* name_fmt = g_statMetaSampleX1.name;
+  char name[MAX_TAB_NAME_SIZE];
+  BaseString::snprintf(name, sizeof(name), name_fmt, g_statMetaSample.tableId);
+  sendGetTabInfoReq(signal, stat, name);
+}
+
+void
+Trix::statMetaGetSampleX1CB(Signal* signal, Uint32 statPtrI, Uint32 ret)
+{
+  StatOp& stat = statOpGetPtr(statPtrI);
+  D("statMetaGetSampleX1CB" << V(stat) << V(ret));
+  StatOp::Meta& meta = stat.m_meta;
+  if (ret != 0)
+  {
+    jam();
+    statOpError(signal, stat, ret, __LINE__);
+    return;
+  }
+  g_statMetaSampleX1.tableId = g_statMetaSample.tableId;
+  g_statMetaSampleX1.indexId = meta.m_conf.tableId;
+  statGetMetaDone(signal, stat);
+}
+
+void
+Trix::sendGetTabInfoReq(Signal* signal, StatOp& stat, const char* name)
+{
+  D("sendGetTabInfoReq" << V(stat) << V(name));
+  GetTabInfoReq* req = (GetTabInfoReq*)signal->getDataPtrSend();
+
+  Uint32 name_len = (Uint32)strlen(name) + 1;
+  Uint32 name_len_words = (name_len + 3 ) / 4;
+  Uint32 name_buf[32];
+  ndbrequire(name_len_words <= 32);
+  memset(name_buf, 0, sizeof(name_buf));
+  memcpy(name_buf, name, name_len);
+
+  req->senderData = stat.m_ownPtrI;
+  req->senderRef = reference();
+  req->requestType = GetTabInfoReq::RequestByName |
+                     GetTabInfoReq::LongSignalConf;
+  req->tableNameLen = name_len;
+  req->schemaTransId = 0;
+  LinearSectionPtr ptr[3];
+  ptr[0].p = name_buf;
+  ptr[0].sz = name_len_words;
+  sendSignal(DBDICT_REF, GSN_GET_TABINFOREQ,
+             signal, GetTabInfoReq::SignalLength, JBB, ptr, 1);
+}
+
+void
+Trix::execGET_TABINFO_CONF(Signal* signal)
+{
+  jamEntry();
+  if (!assembleFragments(signal)) {
+    jam();
+    return;
+  }
+  const GetTabInfoConf* conf = (const GetTabInfoConf*)signal->getDataPtr();
+  StatOp& stat = statOpGetPtr(conf->senderData);
+  D("execGET_TABINFO_CONF" << V(stat));
+  StatOp::Meta& meta = stat.m_meta;
+  meta.m_conf = *conf;
+
+  // do not need DICTTABINFO
+  SectionHandle handle(this, signal);
+  releaseSections(handle);
+
+  execute(signal, meta.m_cb, 0);
+}
+
+void
+Trix::execGET_TABINFO_REF(Signal* signal)
+{
+  jamEntry();
+  const GetTabInfoRef* ref = (const GetTabInfoRef*)signal->getDataPtr();
+  StatOp& stat = statOpGetPtr(ref->senderData);
+  D("execGET_TABINFO_REF" << V(stat));
+  StatOp::Meta& meta = stat.m_meta;
+
+  ndbrequire(ref->errorCode != 0);
+  execute(signal, meta.m_cb, ref->errorCode);
+}
+
+// continue after metadata retrieval
+
+void
+Trix::statGetMetaDone(Signal* signal, StatOp& stat)
+{
+  const IndexStatImplReq* req = &stat.m_req;
+  StatOp::Data& data = stat.m_data;
+  SubscriptionRecord* subRec = c_theSubscriptions.getPtr(stat.m_subRecPtrI);
+  D("statGetMetaDone" << V(stat));
+
+  // c_statGetMetaDone = true;
+
+  subRec->requestType = STAT_UTIL;
+  // fill in constant part
+  ndbrequire(req->fragCount != 0);
+  data.m_indexId = req->indexId;
+  data.m_indexVersion = req->indexVersion;
+  data.m_fragCount = req->fragCount;
+  statHeadRead(signal, stat);
+}
+
+// head table ops
+
+void
+Trix::statHeadRead(Signal* signal, StatOp& stat)
+{
+  StatOp::Util& util = stat.m_util;
+  StatOp::Send& send = stat.m_send;
+  D("statHeadRead" << V(stat));
+
+  util.m_not_found = false;
+  util.m_cb.m_callbackFunction = safe_cast(&Trix::statHeadReadCB);
+  util.m_cb.m_callbackData = stat.m_ownPtrI;
+  send.m_sysTable = &g_statMetaHead;
+  send.m_operationType = UtilPrepareReq::Read;
+  statUtilPrepare(signal, stat);
+}
+
+void
+Trix::statHeadReadCB(Signal* signal, Uint32 statPtrI, Uint32 ret)
+{
+  StatOp& stat = statOpGetPtr(statPtrI);
+  StatOp::Data& data = stat.m_data;
+  StatOp::Util& util = stat.m_util;
+  D("statHeadReadCB" << V(stat) << V(ret));
+
+  ndbrequire(ret == 0);
+  data.m_head_found = !util.m_not_found;
+  statReadHeadDone(signal, stat);
+}
+
+void
+Trix::statHeadInsert(Signal* signal, StatOp& stat)
+{
+  StatOp::Util& util = stat.m_util;
+  StatOp::Send& send = stat.m_send;
+  D("statHeadInsert" << V(stat));
+
+  util.m_cb.m_callbackFunction = safe_cast(&Trix::statHeadInsertCB);
+  util.m_cb.m_callbackData = stat.m_ownPtrI;
+  send.m_sysTable = &g_statMetaHead;
+  send.m_operationType = UtilPrepareReq::Insert;
+  statUtilPrepare(signal, stat);
+}
+
+void
+Trix::statHeadInsertCB(Signal* signal, Uint32 statPtrI, Uint32 ret)
+{
+  StatOp& stat = statOpGetPtr(statPtrI);
+  D("statHeadInsertCB" << V(stat) << V(ret));
+
+  ndbrequire(ret == 0);
+  statInsertHeadDone(signal, stat);
+}
+
+void
+Trix::statHeadUpdate(Signal* signal, StatOp& stat)
+{
+  StatOp::Util& util = stat.m_util;
+  StatOp::Send& send = stat.m_send;
+  D("statHeadUpdate" << V(stat));
+
+  util.m_cb.m_callbackFunction = safe_cast(&Trix::statHeadUpdateCB);
+  util.m_cb.m_callbackData = stat.m_ownPtrI;
+  send.m_sysTable = &g_statMetaHead;
+  send.m_operationType = UtilPrepareReq::Update;
+  statUtilPrepare(signal, stat);
+}
+
+void
+Trix::statHeadUpdateCB(Signal* signal, Uint32 statPtrI, Uint32 ret)
+{
+  StatOp& stat = statOpGetPtr(statPtrI);
+  D("statHeadUpdateCB" << V(stat) << V(ret));
+
+  ndbrequire(ret == 0);
+  statUpdateHeadDone(signal, stat);
+}
+
+void
+Trix::statHeadDelete(Signal* signal, StatOp& stat)
+{
+  StatOp::Util& util = stat.m_util;
+  StatOp::Send& send = stat.m_send;
+  D("statHeadDelete" << V(stat));
+
+  util.m_cb.m_callbackFunction = safe_cast(&Trix::statHeadDeleteCB);
+  util.m_cb.m_callbackData = stat.m_ownPtrI;
+  send.m_sysTable = &g_statMetaHead;
+  send.m_operationType = UtilPrepareReq::Delete;
+  statUtilPrepare(signal, stat);
+}
+
+void
+Trix::statHeadDeleteCB(Signal* signal, Uint32 statPtrI, Uint32 ret)
+{
+  StatOp& stat = statOpGetPtr(statPtrI);
+  D("statHeadDeleteCB" << V(stat) << V(ret));
+
+  ndbrequire(ret == 0);
+  statDeleteHeadDone(signal, stat);
+}
+
+// util (PK ops, only HEAD for now)
+
+void
+Trix::statUtilPrepare(Signal* signal, StatOp& stat)
+{
+  StatOp::Util& util = stat.m_util;
+  D("statUtilPrepare" << V(stat));
+
+  util.m_prepareId = RNIL;
+  statSendPrepare(signal, stat);
+}
+
+void
+Trix::statUtilPrepareConf(Signal* signal, Uint32 statPtrI)
+{
+  StatOp& stat = statOpGetPtr(statPtrI);
+  StatOp::Util& util = stat.m_util;
+  StatOp::Send& send = stat.m_send;
+  D("statUtilPrepareConf" << V(stat));
+
+  const UtilPrepareConf* utilConf =
+    (const UtilPrepareConf*)signal->getDataPtr();
+  util.m_prepareId = utilConf->prepareId;
+
+  const Uint32 ot = send.m_operationType;
+  if (ERROR_INSERTED(18011) && ot == UtilPrepareReq::Read ||
+      ERROR_INSERTED(18012) && ot != UtilPrepareReq::Read)
+  {
+    jam();
+    CLEAR_ERROR_INSERT_VALUE;
+    UtilExecuteRef* utilRef =
+      (UtilExecuteRef*)signal->getDataPtrSend();
+    utilRef->senderData = stat.m_ownPtrI;
+    utilRef->errorCode = UtilExecuteRef::AllocationError;
+    utilRef->TCErrorCode = 0;
+    sendSignal(reference(), GSN_UTIL_EXECUTE_REF,
+               signal, UtilExecuteRef::SignalLength, JBB);
+    return;
+  }
+
+  statUtilExecute(signal, stat);
+}
+
+void
+Trix::statUtilPrepareRef(Signal* signal, Uint32 statPtrI)
+{
+  StatOp& stat = statOpGetPtr(statPtrI);
+  D("statUtilPrepareRef" << V(stat));
+
+  const UtilPrepareRef* utilRef =
+    (const UtilPrepareRef*)signal->getDataPtr();
+  Uint32 errorCode = utilRef->errorCode;
+  ndbrequire(errorCode != 0);
+
+  switch (errorCode) {
+  case UtilPrepareRef::PREPARE_SEIZE_ERROR:
+  case UtilPrepareRef::PREPARE_PAGES_SEIZE_ERROR:
+  case UtilPrepareRef::PREPARED_OPERATION_SEIZE_ERROR:
+    errorCode = IndexStatRef::BusyUtilPrepare;
+    break;
+  case UtilPrepareRef::DICT_TAB_INFO_ERROR:
+    errorCode = IndexStatRef::InvalidSysTable;
+    break;
+  case UtilPrepareRef::MISSING_PROPERTIES_SECTION:
+  default:
+    ndbrequire(false);
+    break;
+  }
+  statOpError(signal, stat, errorCode, __LINE__);
+}
+
+void
+Trix::statUtilExecute(Signal* signal, StatOp& stat)
+{
+  StatOp::Util& util = stat.m_util;
+  StatOp::Send& send = stat.m_send;
+  D("statUtilExecute" << V(stat));
+
+  send.m_prepareId = util.m_prepareId;
+  statSendExecute(signal, stat);
+}
+
+void
+Trix::statUtilExecuteConf(Signal* signal, Uint32 statPtrI)
+{
+  StatOp& stat = statOpGetPtr(statPtrI);
+  StatOp::Attr& attr = stat.m_attr;
+  StatOp::Send& send = stat.m_send;
+  D("statUtilExecuteConf" << V(stat));
+
+  if (send.m_operationType == UtilPrepareReq::Read)
+  {
+    jam();
+    SectionHandle handle(this, signal);
+    Uint32 rattr[20];
+    Uint32 rdata[2048];
+    attr.m_attr = rattr;
+    attr.m_attrMax = 20;
+    attr.m_attrSize = 0;
+    attr.m_data = rdata;
+    attr.m_dataMax = 2048;
+    attr.m_dataSize = 0;
+    {
+      SegmentedSectionPtr ssPtr;
+      handle.getSection(ssPtr, 0);
+      ::copy(rattr, ssPtr);
+    }
+    {
+      SegmentedSectionPtr ssPtr;
+      handle.getSection(ssPtr, 1);
+      ::copy(rdata, ssPtr);
+    }
+    releaseSections(handle);
+
+    const SysTable& sysTable = *send.m_sysTable;
+    for (Uint32 i = 0; i < sysTable.columnCount; i++)
+    {
+      jam();
+      statDataIn(stat, i);
+    }
+  }
+
+  statUtilRelease(signal, stat);
+}
+
+void
+Trix::statUtilExecuteRef(Signal* signal, Uint32 statPtrI)
+{
+  StatOp& stat = statOpGetPtr(statPtrI);
+  StatOp::Util& util = stat.m_util;
+  StatOp::Send& send = stat.m_send;
+  D("statUtilExecuteRef" << V(stat));
+
+  const UtilExecuteRef* utilRef =
+    (const UtilExecuteRef*)signal->getDataPtr();
+  Uint32 errorCode = utilRef->errorCode;
+  ndbrequire(errorCode != 0);
+
+  switch (errorCode) {
+  case UtilExecuteRef::TCError:
+    errorCode = utilRef->TCErrorCode;
+    ndbrequire(errorCode != 0);
+    if (send.m_operationType == UtilPrepareReq::Read &&
+        errorCode == ZNOT_FOUND)
+    {
+      jam();
+      util.m_not_found = true;
+      errorCode = 0;
+    }
+    break;
+  case UtilExecuteRef::AllocationError:
+    errorCode = IndexStatRef::BusyUtilExecute;
+    break;
+  default:
+    ndbrequire(false);
+    break;
+  }
+
+  if (errorCode != 0)
+  {
+    jam();
+    statOpError(signal, stat, errorCode, __LINE__);
+    return;
+  }
+  statUtilRelease(signal, stat);
+}
+
+void
+Trix::statUtilRelease(Signal* signal, StatOp& stat)
+{
+  StatOp::Util& util = stat.m_util;
+  StatOp::Send& send = stat.m_send;
+  D("statUtilRelease" << V(stat));
+
+  send.m_prepareId = util.m_prepareId;
+  statSendRelease(signal, stat);
+}
+
+void
+Trix::statUtilReleaseConf(Signal* signal, Uint32 statPtrI)
+{
+  StatOp& stat = statOpGetPtr(statPtrI);
+  StatOp::Util& util = stat.m_util;
+  D("statUtilReleaseConf" << V(stat));
+
+  util.m_prepareId = RNIL;
+  execute(signal, util.m_cb, 0);
+}
+
+// continue after head table ops
+
+void
+Trix::statReadHeadDone(Signal* signal, StatOp& stat)
+{
+  StatOp::Data& data = stat.m_data;
+  D("statReadHeadDone" << V(stat));
+
+  switch (stat.m_requestType) {
+  case IndexStatReq::RT_CLEAN_NEW:
+    jam();
+  case IndexStatReq::RT_CLEAN_OLD:
+    jam();
+  case IndexStatReq::RT_CLEAN_ALL:
+    jam();
+    statCleanBegin(signal, stat);
+    break;
+
+  case IndexStatReq::RT_SCAN_FRAG:
+    jam();
+    statScanBegin(signal, stat);
+    break;
+
+  case IndexStatReq::RT_DROP_HEAD:
+    jam();
+    statDropBegin(signal, stat);
+    break;
+
+  default:
+    ndbrequire(false);
+    break;
+  }
+}
+
+void
+Trix::statInsertHeadDone(Signal* signal, StatOp& stat)
+{
+  D("statInsertHeadDone" << V(stat));
+
+  switch (stat.m_requestType) {
+  case IndexStatReq::RT_SCAN_FRAG:
+    jam();
+    statScanEnd(signal, stat);
+    break;
+  default:
+    ndbrequire(false);
+    break;
+  }
+}
+
+void
+Trix::statUpdateHeadDone(Signal* signal, StatOp& stat)
+{
+  D("statUpdateHeadDone" << V(stat));
+
+  switch (stat.m_requestType) {
+  case IndexStatReq::RT_SCAN_FRAG:
+    jam();
+    statScanEnd(signal, stat);
+    break;
+  default:
+    ndbrequire(false);
+    break;
+  }
+}
+
+void
+Trix::statDeleteHeadDone(Signal* signal, StatOp& stat)
+{
+  D("statDeleteHeadDone" << V(stat));
+
+  switch (stat.m_requestType) {
+  case IndexStatReq::RT_DROP_HEAD:
+    jam();
+    statDropEnd(signal, stat);
+    break;
+  default:
+    ndbrequire(false);
+    break;
+  }
+}
+
+// clean
+
+void
+Trix::statCleanBegin(Signal* signal, StatOp& stat)
+{
+  const IndexStatImplReq* req = &stat.m_req;
+  StatOp::Data& data = stat.m_data;
+  D("statCleanBegin" << V(stat));
+
+  if (data.m_head_found == true)
+  {
+    jam();
+    if (data.m_tableId != req->tableId &&
+        stat.m_requestType != IndexStatReq::RT_CLEAN_ALL)
+    {
+      jam();
+      // must run ndb_index_stat --drop
+      statOpError(signal, stat, IndexStatRef::InvalidSysTableData, __LINE__);
+      return;
+    }
+  }
+  else
+  {
+    if (stat.m_requestType != IndexStatReq::RT_CLEAN_ALL)
+    {
+      jam();
+      // happens normally on first stats scan
+      stat.m_requestType = IndexStatReq::RT_CLEAN_ALL;
+    }
+  }
+  statCleanPrepare(signal, stat);
+}
+
+void
+Trix::statCleanPrepare(Signal* signal, StatOp& stat)
+{
+  const IndexStatImplReq* req = &stat.m_req;
+  StatOp::Data& data = stat.m_data;
+  StatOp::Clean& clean = stat.m_clean;
+  StatOp::Send& send = stat.m_send;
+  SubscriptionRecord* subRec = c_theSubscriptions.getPtr(stat.m_subRecPtrI);
+  D("statCleanPrepare" << V(stat));
+
+  // count of deleted samples is just for info
+  clean.m_cleanCount = 0;
+
+  const Uint32 ao_list[] = {
+    0,  // INDEX_ID
+    1,  // INDEX_VERSION
+    2,  // SAMPLE_VERSION
+    3   // STAT_KEY
+  };
+  const Uint32 ao_size = sizeof(ao_list)/sizeof(ao_list[0]);
+
+  ndbrequire(req->fragId == ZNIL);
+  subRec->m_flags = 0;
+  subRec->requestType = STAT_CLEAN;
+  subRec->schemaTransId = req->transId;
+  subRec->userReference = 0; // not used
+  subRec->connectionPtr = RNIL;
+  subRec->subscriptionId = rand();
+  subRec->subscriptionKey = rand();
+  subRec->prepareId = RNIL;
+  subRec->indexType = 0; // not used
+  subRec->sourceTableId = g_statMetaSampleX1.indexId;
+  subRec->targetTableId = RNIL;
+  subRec->noOfIndexColumns = ao_size;
+  subRec->noOfKeyColumns = 0;
+  subRec->parallelism = 16;
+  subRec->fragCount = 0;
+  subRec->fragId = ZNIL;
+  subRec->syncPtr = RNIL;
+  subRec->errorCode = BuildIndxRef::NoError;
+  subRec->subscriptionCreated = false;
+  subRec->pendingSubSyncContinueConf = false;
+  subRec->expectedConf = 0;
+  subRec->m_rows_processed = 0;
+  subRec->m_gci = 0;
+
+  AttrOrderBuffer& ao_buf = subRec->attributeOrder;
+  ndbrequire(ao_buf.isEmpty());
+  ao_buf.append(ao_list, ao_size);
+
+  // create TUX bounds
+  clean.m_bound[0] = TuxBoundInfo::BoundEQ;
+  clean.m_bound[1] = AttributeHeader(0, 4).m_value;
+  clean.m_bound[2] = data.m_indexId;
+  clean.m_bound[3] = TuxBoundInfo::BoundEQ;
+  clean.m_bound[4] = AttributeHeader(1, 4).m_value;
+  clean.m_bound[5] = data.m_indexVersion;
+  switch (stat.m_requestType) {
+  case IndexStatReq::RT_CLEAN_NEW:
+    D("statCleanPrepare delete sample versions > " << data.m_sampleVersion);
+    clean.m_bound[6] = TuxBoundInfo::BoundLT;
+    clean.m_bound[7] = AttributeHeader(2, 4).m_value;
+    clean.m_bound[8] = data.m_sampleVersion;
+    clean.m_boundCount = 3;
+    break;
+  case IndexStatReq::RT_CLEAN_OLD:
+    D("statCleanPrepare delete sample versions < " << data.m_sampleVersion);
+    clean.m_bound[6] = TuxBoundInfo::BoundGT;
+    clean.m_bound[7] = AttributeHeader(2, 4).m_value;
+    clean.m_bound[8] = data.m_sampleVersion;
+    clean.m_boundCount = 3;
+    break;
+  case IndexStatReq::RT_CLEAN_ALL:
+    D("statCleanPrepare delete all sample versions");
+    clean.m_boundCount = 2;
+    break;
+  default:
+    ndbrequire(false);
+    break;
+  }
+  clean.m_boundSize = 3 * clean.m_boundCount;
+
+  // TRIX traps the CONF
+  send.m_sysTable = &g_statMetaSample;
+  send.m_operationType = UtilPrepareReq::Delete;
+  statSendPrepare(signal, stat);
+}
+
+void
+Trix::statCleanExecute(Signal* signal, StatOp& stat)
+{
+  StatOp::Data& data = stat.m_data;
+  StatOp::Send& send = stat.m_send;
+  StatOp::Clean& clean = stat.m_clean;
+  SubscriptionRecord* subRec = c_theSubscriptions.getPtr(stat.m_subRecPtrI);
+  D("statCleanExecute" << V(stat));
+
+  SectionHandle handle(this, signal);
+  ndbrequire(handle.m_cnt == 2);
+
+  // ATTR_INFO
+  AttributeHeader ah[4];
+  SegmentedSectionPtr ptr0;
+  handle.getSection(ptr0, SubTableData::ATTR_INFO);
+  ndbrequire(ptr0.sz == 4);
+  ::copy((Uint32*)ah, ptr0);
+  ndbrequire(ah[0].getAttributeId() == 0 && ah[0].getDataSize() == 1);
+  ndbrequire(ah[1].getAttributeId() == 1 && ah[1].getDataSize() == 1);
+  ndbrequire(ah[2].getAttributeId() == 2 && ah[2].getDataSize() == 1);
+  // read via TUP rounds bytes to words
+  const Uint32 kz = ah[3].getDataSize();
+  ndbrequire(ah[3].getAttributeId() == 3 && kz != 0);
+
+  // AFTER_VALUES
+  const Uint32 avmax = 3 + MAX_INDEX_STAT_KEY_SIZE;
+  Uint32 av[avmax];
+  SegmentedSectionPtr ptr1;
+  handle.getSection(ptr1, SubTableData::AFTER_VALUES);
+  ndbrequire(ptr1.sz <= avmax);
+  ::copy(av, ptr1);
+  ndbrequire(data.m_indexId == av[0]);
+  ndbrequire(data.m_indexVersion == av[1]);
+  data.m_sampleVersion = av[2];
+  data.m_statKey = &av[3];
+  const char* kp = (const char*)data.m_statKey;
+  const Uint32 kb = kp[0] + (kp[1] << 8);
+  // key is not empty
+  ndbrequire(kb != 0);
+  ndbrequire(kz == ((2 + kb) + 3) / 4);
+
+  clean.m_cleanCount++;
+  releaseSections(handle);
+
+  const Uint32 rt = stat.m_requestType;
+  if (ERROR_INSERTED(18021) && rt == IndexStatReq::RT_CLEAN_NEW ||
+      ERROR_INSERTED(18022) && rt == IndexStatReq::RT_CLEAN_OLD ||
+      ERROR_INSERTED(18023) && rt == IndexStatReq::RT_CLEAN_ALL)
+  {
+    jam();
+    CLEAR_ERROR_INSERT_VALUE;
+    UtilExecuteRef* utilRef =
+      (UtilExecuteRef*)signal->getDataPtrSend();
+    utilRef->senderData = stat.m_ownPtrI;
+    utilRef->errorCode = UtilExecuteRef::TCError;
+    utilRef->TCErrorCode = 626;
+    sendSignal(reference(), GSN_UTIL_EXECUTE_REF,
+               signal, UtilExecuteRef::SignalLength, JBB);
+    subRec->expectedConf++;
+    return;
+  }
+
+  // TRIX traps the CONF
+  send.m_sysTable = &g_statMetaSample;
+  send.m_operationType = UtilPrepareReq::Delete;
+  send.m_prepareId = subRec->prepareId;
+  subRec->expectedConf++;
+  statSendExecute(signal, stat);
+}
+
+void
+Trix::statCleanRelease(Signal* signal, StatOp& stat)
+{
+  SubscriptionRecord* subRec = c_theSubscriptions.getPtr(stat.m_subRecPtrI);
+  D("statCleanRelease" << V(stat) << V(subRec->errorCode));
+
+  if (subRec->errorCode != 0)
+  {
+    jam();
+    statOpError(signal, stat, subRec->errorCode, __LINE__);
+    return;
+  }
+  statCleanEnd(signal, stat);
+}
+
+void
+Trix::statCleanEnd(Signal* signal, StatOp& stat)
+{
+  D("statCleanEnd" << V(stat));
+  statOpSuccess(signal, stat);
+}
+
+// scan
+
+void
+Trix::statScanBegin(Signal* signal, StatOp& stat)
+{
+  const IndexStatImplReq* req = &stat.m_req;
+  StatOp::Data& data = stat.m_data;
+  D("statScanBegin" << V(stat));
+
+  if (data.m_head_found == true &&
+      data.m_tableId != req->tableId)
+  {
+    jam();
+    statOpError(signal, stat, IndexStatRef::InvalidSysTableData, __LINE__);
+    return;
+  }
+  data.m_tableId = req->tableId;
+  statScanPrepare(signal, stat);
+}
+
+void
+Trix::statScanPrepare(Signal* signal, StatOp& stat)
+{
+  const IndexStatImplReq* req = &stat.m_req;
+  StatOp::Data& data = stat.m_data;
+  StatOp::Scan& scan = stat.m_scan;
+  StatOp::Send& send = stat.m_send;
+  SubscriptionRecord* subRec = c_theSubscriptions.getPtr(stat.m_subRecPtrI);
+  D("statScanPrepare" << V(stat));
+
+  // update sample version prior to scan
+  if (data.m_head_found == false)
+    data.m_sampleVersion = 0;
+  data.m_sampleVersion += 1;
+
+  // zero totals
+  scan.m_sampleCount = 0;
+  scan.m_keyBytes = 0;
+
+  const Uint32 ao_list[] = {
+    AttributeHeader::INDEX_STAT_KEY,
+    AttributeHeader::INDEX_STAT_VALUE
+  };
+  const Uint32 ao_size = sizeof(ao_list)/sizeof(ao_list[0]);
+
+  ndbrequire(req->fragId != ZNIL);
+  subRec->m_flags = 0;
+  subRec->requestType = STAT_SCAN;
+  subRec->schemaTransId = req->transId;
+  subRec->userReference = 0; // not used
+  subRec->connectionPtr = RNIL;
+  subRec->subscriptionId = rand();
+  subRec->subscriptionKey = rand();
+  subRec->prepareId = RNIL;
+  subRec->indexType = 0; // not used
+  subRec->sourceTableId = data.m_indexId;
+  subRec->targetTableId = RNIL;
+  subRec->noOfIndexColumns = ao_size;
+  subRec->noOfKeyColumns = 0;
+  subRec->parallelism = 16;
+  subRec->fragCount = 0; // XXX Suma currently checks all frags
+  subRec->fragId = req->fragId;
+  subRec->syncPtr = RNIL;
+  subRec->errorCode = BuildIndxRef::NoError;
+  subRec->subscriptionCreated = false;
+  subRec->pendingSubSyncContinueConf = false;
+  subRec->expectedConf = 0;
+  subRec->m_rows_processed = 0;
+  subRec->m_gci = 0;
+
+  AttrOrderBuffer& ao_buf = subRec->attributeOrder;
+  ndbrequire(ao_buf.isEmpty());
+  ao_buf.append(ao_list, ao_size);
+
+  // TRIX traps the CONF
+  send.m_sysTable = &g_statMetaSample;
+  send.m_operationType = UtilPrepareReq::Insert;
+  statSendPrepare(signal, stat);
+}
+
+void
+Trix::statScanExecute(Signal* signal, StatOp& stat)
+{
+  StatOp::Data& data = stat.m_data;
+  StatOp::Scan& scan = stat.m_scan;
+  StatOp::Send& send = stat.m_send;
+  SubscriptionRecord* subRec = c_theSubscriptions.getPtr(stat.m_subRecPtrI);
+  D("statScanExecute" << V(stat));
+
+  SectionHandle handle(this, signal);
+  ndbrequire(handle.m_cnt == 2);
+
+  // ATTR_INFO
+  AttributeHeader ah[2];
+  SegmentedSectionPtr ptr0;
+  handle.getSection(ptr0, SubTableData::ATTR_INFO);
+  ndbrequire(ptr0.sz == 2);
+  ::copy((Uint32*)ah, ptr0);
+  ndbrequire(ah[0].getAttributeId() == AttributeHeader::INDEX_STAT_KEY);
+  ndbrequire(ah[1].getAttributeId() == AttributeHeader::INDEX_STAT_VALUE);
+  // read via TUP rounds bytes to words
+  const Uint32 kz = ah[0].getDataSize();
+  const Uint32 vz = ah[1].getDataSize();
+  ndbrequire(kz != 0 && vz != 0);
+
+  // AFTER_VALUES
+  const Uint32 avmax = MAX_INDEX_STAT_KEY_SIZE + MAX_INDEX_STAT_VALUE_SIZE;
+  Uint32 av[avmax];
+  SegmentedSectionPtr ptr1;
+  handle.getSection(ptr1, SubTableData::AFTER_VALUES);
+  ndbrequire(ptr1.sz <= avmax);
+  ::copy(av, ptr1);
+  data.m_statKey = &av[0];
+  data.m_statValue = &av[kz];
+  const char* kp = (const char*)data.m_statKey;
+  const char* vp = (const char*)data.m_statValue;
+  const Uint32 kb = kp[0] + (kp[1] << 8);
+  const Uint32 vb = vp[0] + (vp[1] << 8);
+  // key and value are not empty
+  ndbrequire(kb != 0 && vb != 0);
+  ndbrequire(kz == ((2 + kb) + 3) / 4);
+  ndbrequire(vz == ((2 + vb) + 3) / 4);
+
+  scan.m_sampleCount++;
+  scan.m_keyBytes += kb;
+  releaseSections(handle);
+
+  if (ERROR_INSERTED(18024))
+  {
+    jam();
+    CLEAR_ERROR_INSERT_VALUE;
+    UtilExecuteRef* utilRef =
+      (UtilExecuteRef*)signal->getDataPtrSend();
+    utilRef->senderData = stat.m_ownPtrI;
+    utilRef->errorCode = UtilExecuteRef::TCError;
+    utilRef->TCErrorCode = 630;
+    sendSignal(reference(), GSN_UTIL_EXECUTE_REF,
+               signal, UtilExecuteRef::SignalLength, JBB);
+    subRec->expectedConf++;
+    return;
+  }
+
+  // TRIX traps the CONF
+  send.m_sysTable = &g_statMetaSample;
+  send.m_operationType = UtilPrepareReq::Insert;
+  send.m_prepareId = subRec->prepareId;
+  subRec->expectedConf++;
+  statSendExecute(signal, stat);
+}
+
+void
+Trix::statScanRelease(Signal* signal, StatOp& stat)
+{
+  StatOp::Data& data = stat.m_data;
+  StatOp::Scan& scan = stat.m_scan;
+  SubscriptionRecord* subRec = c_theSubscriptions.getPtr(stat.m_subRecPtrI);
+  D("statScanRelease" << V(stat) << V(subRec->errorCode));
+
+  if (subRec->errorCode != 0)
+  {
+    jam();
+    statOpError(signal, stat, subRec->errorCode, __LINE__);
+    return;
+  }
+  subRec->requestType = STAT_UTIL;
+
+  const Uint32 now = (Uint32)time(0);
+  data.m_loadTime = now;
+  data.m_sampleCount = scan.m_sampleCount;
+  data.m_keyBytes = scan.m_keyBytes;
+  data.m_valueFormat = MAX_INDEX_STAT_VALUE_FORMAT;
+
+  if (data.m_head_found == false)
+  {
+    jam();
+    statHeadInsert(signal, stat);
+  }
+  else
+  {
+    jam();
+    statHeadUpdate(signal, stat);
+  }
+}
+
+void
+Trix::statScanEnd(Signal* signal, StatOp& stat)
+{
+  StatOp::Data& data = stat.m_data;
+  const IndexStatImplReq* req = &stat.m_req;
+  D("statScanEnd" << V(stat));
+
+  /*
+   * TRIX reports stats load time to TUX for proper stats monitoring.
+   * Passing this via DBDICT RT_START_MON is not feasible.  For MT-LQH
+   * we prefer DbtuxProxy to avoid introducing MT-LQH into TRIX.
+   */
+
+#if trix_index_stat_rep_to_tux_instance
+  Uint32 instanceKey = getInstanceKey(req->indexId, req->fragId);
+  BlockReference tuxRef = numberToRef(DBTUX, instanceKey, getOwnNodeId());
+#else
+  BlockReference tuxRef = DBTUX_REF;
+#endif
+
+  IndexStatRep* rep = (IndexStatRep*)signal->getDataPtrSend();
+  rep->senderRef = reference();
+  rep->senderData = 0;
+  rep->requestType = IndexStatRep::RT_UPDATE_CONF;
+  rep->requestFlag = 0;
+  rep->indexId = req->indexId;
+  rep->indexVersion = req->indexVersion;
+  rep->tableId = req->tableId;
+  rep->fragId = req->fragId;
+  rep->loadTime = data.m_loadTime;
+  sendSignal(tuxRef, GSN_INDEX_STAT_REP,
+             signal, IndexStatRep::SignalLength, JBB);
+
+  statOpSuccess(signal, stat);
+}
+
+// drop
+
+void
+Trix::statDropBegin(Signal* signal, StatOp& stat)
+{
+  StatOp::Data& data = stat.m_data;
+  D("statDropBegin" << V(stat));
+
+  if (data.m_head_found == true)
+  {
+    jam();
+    statHeadDelete(signal, stat);
+    return;
+  }
+  statDropEnd(signal, stat);
+}
+
+void
+Trix::statDropEnd(Signal* signal, StatOp& stat)
+{
+  D("statDropEnd");
+  statOpSuccess(signal, stat);
+}
+
+// send
+
+void
+Trix::statSendPrepare(Signal* signal, StatOp& stat)
+{
+  StatOp::Send& send = stat.m_send;
+  const IndexStatImplReq* req = &stat.m_req;
+  const SysTable& sysTable = *send.m_sysTable;
+  D("statSendPrepare" << V(stat));
+
+  UtilPrepareReq* utilReq =
+    (UtilPrepareReq*)signal->getDataPtrSend();
+  utilReq->senderData = stat.m_ownPtrI;
+  utilReq->senderRef = reference();
+  utilReq->schemaTransId = req->transId;
+
+  Uint32 wbuf[256];
+  LinearWriter w(&wbuf[0], sizeof(wbuf) >> 2);
+
+  w.first();
+  w.add(UtilPrepareReq::NoOfOperations, 1);
+  w.add(UtilPrepareReq::OperationType, send.m_operationType);
+  w.add(UtilPrepareReq::TableId, sysTable.tableId);
+
+  Uint32 i;
+  for (i = 0; i < sysTable.columnCount; i++) {
+    const SysColumn& c = sysTable.columnList[i];
+    switch (send.m_operationType) {
+    case UtilPrepareReq::Read:
+    case UtilPrepareReq::Insert:
+    case UtilPrepareReq::Update:
+      jam();
+      w.add(UtilPrepareReq::AttributeId, i);
+      break;
+    case UtilPrepareReq::Delete:
+      jam();
+      if (c.keyFlag)
+        w.add(UtilPrepareReq::AttributeId, i);
+      break;
+    default:
+      ndbrequire(false);
+      break;
+    }
+  }
+
+  LinearSectionPtr ptr[3];
+  ptr[0].p = &wbuf[0];
+  ptr[0].sz = w.getWordsUsed();
+  sendSignal(DBUTIL_REF, GSN_UTIL_PREPARE_REQ,
+             signal, UtilPrepareReq::SignalLength, JBB, ptr, 1);
+}
+
+void
+Trix::statSendExecute(Signal* signal, StatOp& stat)
+{
+  D("statSendExecute" << V(stat));
+  StatOp::Send& send = stat.m_send;
+  StatOp::Attr& attr = stat.m_attr;
+  const SysTable& sysTable = *send.m_sysTable;
+
+  UtilExecuteReq* utilReq =
+    (UtilExecuteReq*)signal->getDataPtrSend();
+  utilReq->senderData = stat.m_ownPtrI;
+  utilReq->senderRef = reference();
+  utilReq->prepareId = send.m_prepareId;
+  utilReq->scanTakeOver = 0;
+
+  Uint32 wattr[20];
+  Uint32 wdata[2048];
+  attr.m_attr = wattr;
+  attr.m_attrMax = 20;
+  attr.m_attrSize = 0;
+  attr.m_data = wdata;
+  attr.m_dataMax = 2048;
+  attr.m_dataSize = 0;
+
+  for (Uint32 i = 0; i < sysTable.columnCount; i++) {
+    const SysColumn& c = sysTable.columnList[i];
+    switch (send.m_operationType) {
+    case UtilPrepareReq::Read:
+    case UtilPrepareReq::Insert:
+    case UtilPrepareReq::Update:
+      jam();
+      statDataOut(stat, i);
+      break;
+    case UtilPrepareReq::Delete:
+      jam();
+      if (c.keyFlag)
+        statDataOut(stat, i);
+      break;
+    default:
+      ndbrequire(false);
+      break;
+    }
+  }
+
+  LinearSectionPtr ptr[3];
+  ptr[0].p = attr.m_attr;
+  ptr[0].sz = attr.m_attrSize;
+  ptr[1].p = attr.m_data;
+  ptr[1].sz = attr.m_dataSize;
+  sendSignal(DBUTIL_REF, GSN_UTIL_EXECUTE_REQ,
+             signal, UtilExecuteReq::SignalLength, JBB, ptr, 2);
+}
+
+void
+Trix::statSendRelease(Signal* signal, StatOp& stat)
+{
+  D("statSendRelease" << V(stat));
+  StatOp::Send& send = stat.m_send;
+  ndbrequire(send.m_prepareId != RNIL);
+
+  UtilReleaseReq* utilReq =
+    (UtilReleaseReq*)signal->getDataPtrSend();
+  utilReq->senderData = stat.m_ownPtrI;
+  utilReq->prepareId = send.m_prepareId;
+  sendSignal(DBUTIL_REF, GSN_UTIL_RELEASE_REQ,
+             signal, UtilReleaseReq::SignalLength, JBB);
+}
+
+// data
+
+void
+Trix::statDataPtr(StatOp& stat, Uint32 i, Uint32*& dptr, Uint32& bytes)
+{
+  StatOp::Data& data = stat.m_data;
+  StatOp::Send& send = stat.m_send;
+
+  const SysTable& sysTable = *send.m_sysTable;
+  ndbrequire(i < sysTable.columnCount);
+  const SysColumn& c = sysTable.columnList[i];
+
+  if (&sysTable == &g_statMetaHead)
+  {
+    switch (i) {
+    case 0:
+      dptr = &data.m_indexId;
+      bytes = 4;
+      break;
+    case 1:
+      dptr = &data.m_indexVersion;
+      bytes = 4;
+      break;
+    case 2:
+      dptr = &data.m_tableId;
+      bytes = 4;
+      break;
+    case 3:
+      dptr = &data.m_fragCount;
+      bytes = 4;
+      break;
+    case 4:
+      dptr = &data.m_valueFormat;
+      bytes = 4;
+      break;
+    case 5:
+      dptr = &data.m_sampleVersion;
+      bytes = 4;
+      break;
+    case 6:
+      dptr = &data.m_loadTime;
+      bytes = 4;
+      break;
+    case 7:
+      dptr = &data.m_sampleCount;
+      bytes = 4;
+      break;
+    case 8:
+      dptr = &data.m_keyBytes;
+      bytes = 4;
+      break;
+    default:
+      ndbrequire(false);
+      break;
+    }
+    return;
+  }
+
+  if (&sysTable == &g_statMetaSample)
+  {
+    switch (i) {
+    case 0:
+      dptr = &data.m_indexId;
+      bytes = 4;
+      break;
+    case 1:
+      dptr = &data.m_indexVersion;
+      bytes = 4;
+      break;
+    case 2:
+      dptr = &data.m_sampleVersion;
+      bytes = 4;
+      break;
+    case 3:
+      {
+        dptr = data.m_statKey;
+        const uchar* p = (uchar*)dptr;
+        ndbrequire(p != 0);
+        bytes = 2 + p[0] + (p[1] << 8);
+      }
+      break;
+    case 4:
+      {
+        dptr = data.m_statValue;
+        const uchar* p = (uchar*)dptr;
+        ndbrequire(p != 0);
+        bytes = 2 + p[0] + (p[1] << 8);
+      }
+      break;
+    default:
+      ndbrequire(false);
+      break;
+    }
+    return;
+  }
+
+  ndbrequire(false);
+}
+
+void
+Trix::statDataOut(StatOp& stat, Uint32 i)
+{
+  StatOp::Attr& attr = stat.m_attr;
+  Uint32* dptr = 0;
+  Uint32 bytes = 0;
+  statDataPtr(stat, i, dptr, bytes);
+
+  ndbrequire(attr.m_attrSize + 1 <= attr.m_attrMax);
+  AttributeHeader::init(&attr.m_attr[attr.m_attrSize], i, bytes);
+  attr.m_attrSize++;
+
+  Uint32 words = (bytes + 3) / 4;
+  ndbrequire(attr.m_dataSize + words <= attr.m_dataMax);
+  Uint8* dst = (Uint8*)&attr.m_data[attr.m_dataSize];
+  memcpy(dst, dptr, bytes);
+  while (bytes < words * 4)
+    dst[bytes++] = 0;
+  attr.m_dataSize += words;
+  D("statDataOut" << V(i) << V(bytes) << hex << V(dptr[0]));
+}
+
+void
+Trix::statDataIn(StatOp& stat, Uint32 i)
+{
+  StatOp::Attr& attr = stat.m_attr;
+  Uint32* dptr = 0;
+  Uint32 bytes = 0;
+  statDataPtr(stat, i, dptr, bytes);
+
+  ndbrequire(attr.m_attrSize + 1 <= attr.m_attrMax);
+  const AttributeHeader& ah = attr.m_attr[attr.m_attrSize];
+  attr.m_attrSize++;
+
+  ndbrequire(ah.getByteSize() == bytes);
+  Uint32 words = (bytes + 3) / 4;
+  ndbrequire(attr.m_dataSize + words <= attr.m_dataMax);
+  const char* src = (const char*)&attr.m_data[attr.m_dataSize];
+  memcpy(dptr, src, bytes);
+  attr.m_dataSize += words;
+  D("statDataIn" << V(i) << V(bytes) << hex << V(dptr[0]));
+}
+
+// abort ongoing
+
+void
+Trix::statAbortUtil(Signal* signal, StatOp& stat)
+{
+  StatOp::Util& util = stat.m_util;
+  D("statAbortUtil" << V(stat));
+
+  ndbrequire(util.m_prepareId != RNIL);
+  util.m_cb.m_callbackFunction = safe_cast(&Trix::statAbortUtilCB);
+  util.m_cb.m_callbackData = stat.m_ownPtrI;
+  statUtilRelease(signal, stat);
+}
+
+void
+Trix::statAbortUtilCB(Signal* signal, Uint32 statPtrI, Uint32 ret)
+{
+  StatOp& stat = statOpGetPtr(statPtrI);
+  D("statAbortUtilCB" << V(stat) << V(ret));
+
+  ndbrequire(ret == 0);
+  statOpAbort(signal, stat);
+}
+
+// conf and ref
+
+void
+Trix::statOpSuccess(Signal* signal, StatOp& stat)
+{
+  StatOp::Data& data = stat.m_data;
+  D("statOpSuccess" << V(stat));
+
+  if (stat.m_requestType == IndexStatReq::RT_SCAN_FRAG)
+    statOpEvent(stat, "I", "created %u samples", data.m_sampleCount);
+
+  statOpConf(signal, stat);
+  statOpRelease(stat);
+}
+
+void
+Trix::statOpConf(Signal* signal, StatOp& stat)
+{
+  const IndexStatImplReq* req = &stat.m_req;
+  D("statOpConf" << V(stat));
+
+  IndexStatImplConf* conf = (IndexStatImplConf*)signal->getDataPtrSend();
+  conf->senderRef = reference();
+  conf->senderData = req->senderData;
+  sendSignal(req->senderRef, GSN_INDEX_STAT_IMPL_CONF,
+             signal, IndexStatImplConf::SignalLength, JBB);
+}
+
+void
+Trix::statOpError(Signal* signal, StatOp& stat,
+                   Uint32 errorCode, Uint32 errorLine)
+{
+  D("statOpError" << V(stat) << V(errorCode) << V(errorLine));
+
+  statOpEvent(stat, "W", "error %u line %u", errorCode, errorLine);
+
+  ndbrequire(stat.m_errorCode == 0);
+  stat.m_errorCode = errorCode;
+  stat.m_errorLine = errorLine;
+  statOpAbort(signal, stat);
+}
+
+void
+Trix::statOpAbort(Signal* signal, StatOp& stat)
+{
+  StatOp::Util& util = stat.m_util;
+  D("statOpAbort" << V(stat));
+  
+  if (util.m_prepareId != RNIL)
+  {
+    jam();
+    // returns here when done
+    statAbortUtil(signal, stat);
+    return;
+  }
+  statOpRef(signal, stat);
+  statOpRelease(stat);
+}
+
+void
+Trix::statOpRef(Signal* signal, StatOp& stat)
+{
+  const IndexStatImplReq* req = &stat.m_req;
+  D("statOpRef" << V(stat));
+
+  statOpRef(signal, req, stat.m_errorCode, stat.m_errorLine);
+}
+
+void
+Trix::statOpRef(Signal* signal, const IndexStatImplReq* req,
+                Uint32 errorCode, Uint32 errorLine)
+{
+  D("statOpRef" << V(errorCode) << V(errorLine));
+
+  IndexStatImplRef* ref = (IndexStatImplRef*)signal->getDataPtrSend();
+  ref->senderRef = reference();
+  ref->senderData = req->senderData;
+  ref->errorCode = errorCode;
+  ref->errorLine = errorLine;
+  sendSignal(req->senderRef, GSN_INDEX_STAT_IMPL_REF,
+             signal, IndexStatImplRef::SignalLength, JBB);
+}
+
+void
+Trix::statOpEvent(StatOp& stat, const char* level, const char* msg, ...)
+{
+  const IndexStatImplReq* req = &stat.m_req;
+  StatOp::Data& data = stat.m_data;
+
+  char tmp1[100];
+  va_list ap;
+  va_start(ap, msg);
+  BaseString::vsnprintf(tmp1, sizeof(tmp1), msg, ap);
+  va_end(ap);
+
+  char tmp2[100];
+  BaseString::snprintf(tmp2, sizeof(tmp2),
+                       "index %u stats version %u: %s: %s",
+                       data.m_indexId, data.m_sampleVersion,
+                       stat.m_requestName, tmp1);
+
+  D("statOpEvent" << V(level) << V(tmp2));
+
+  if (level[0] == 'I')
+    infoEvent("%s", tmp2);
+  if (level[0] == 'W')
+    warningEvent("%s", tmp2);
+}
+
+// debug
+
+class NdbOut&
+operator<<(NdbOut& out, const Trix::StatOp& stat)
+{
+  out << "[";
+  out << " i:" << stat.m_ownPtrI;
+  out << " head_found:" << stat.m_data.m_head_found;
+  out << " ]";
+  return out;
+}
+
 
 BLOCK_FUNCTIONS(Trix)
 

=== modified file 'storage/ndb/src/kernel/blocks/trix/Trix.hpp'
--- a/storage/ndb/src/kernel/blocks/trix/Trix.hpp	2011-02-01 23:27:25 +0000
+++ b/storage/ndb/src/kernel/blocks/trix/Trix.hpp	2011-05-30 08:24:14 +0000
@@ -25,6 +25,10 @@
 #include <signaldata/DictTabInfo.hpp>
 #include <signaldata/CreateTrig.hpp>
 #include <signaldata/BuildIndx.hpp>
+#include <signaldata/IndexStatSignal.hpp>
+#include <signaldata/GetTabInfo.hpp>
+#include <signaldata/TuxBound.hpp>
+#define ZNOT_FOUND 626
 
 // Error codes
 #define INTERNAL_ERROR_ILLEGAL_CALL 4344
@@ -46,6 +50,9 @@ public:
     REORG_COPY = 0
     ,REORG_DELETE = 1
     ,INDEX_BUILD = 2
+    ,STAT_UTIL = 3  // PK op of HEAD table directly via DBUTIL
+    ,STAT_CLEAN = 4
+    ,STAT_SCAN = 5
     //ALTER_TABLE
   };
   typedef DataBuffer<11> AttrOrderBuffer;
@@ -126,6 +133,7 @@ private:
     Uint32 noOfKeyColumns;
     Uint32 parallelism;
     Uint32 fragCount;
+    Uint32 fragId;
     Uint32 syncPtr;
     BuildIndxRef::ErrorCode errorCode;
     bool subscriptionCreated;
@@ -133,6 +141,7 @@ private:
     Uint32 expectedConf; // Count in n UTIL_EXECUTE_CONF + 1 SUB_SYNC_CONF
     Uint64 m_rows_processed;
     Uint64 m_gci;
+    Uint32 m_statPtrI;
     union {
       Uint32 nextPool;
       Uint32 nextList;
@@ -153,6 +162,127 @@ private:
    */  
   DLList<SubscriptionRecord> c_theSubscriptions;
 
+  /*
+   * Ordered index stats.  Implements sub-ops of DBDICT index stat
+   * schema op.  Each sub-op is a simple REQ which seizes and releases
+   * a stat op here before returning CONF or REF.  A stat op always has
+   * an associated SubscriptionRecord.  It is used for SUMA index scans
+   * and as proxy for PK ops to DBUTIL.
+   */
+
+  bool c_statGetMetaDone;
+  struct SysColumn {
+    Uint32 pos;
+    const char* name;
+    bool keyFlag;
+  };
+  struct SysTable {
+    const char* name;
+    mutable Uint32 tableId;
+    const Uint32 columnCount;
+    const SysColumn* columnList;
+  };
+  struct SysIndex {
+    const char* name;
+    mutable Uint32 tableId;
+    mutable Uint32 indexId;
+  };
+  static const SysColumn g_statMetaHead_column[];
+  static const SysColumn g_statMetaSample_column[];
+  static const SysTable g_statMetaHead;
+  static const SysTable g_statMetaSample;
+  static const SysIndex g_statMetaSampleX1;
+
+  struct StatOp {
+    struct Meta {
+      GetTabInfoConf m_conf;
+      Callback m_cb;
+    };
+    struct Data {
+      Int32 m_head_found;
+      Uint32 m_indexId;
+      Uint32 m_indexVersion;
+      Uint32 m_tableId;
+      Uint32 m_fragCount;
+      Uint32 m_valueFormat;
+      Uint32 m_sampleVersion;
+      Uint32 m_loadTime;
+      Uint32 m_sampleCount;
+      Uint32 m_keyBytes;
+      Uint32* m_statKey;
+      Uint32* m_statValue;
+      Data() {
+        m_head_found = -1;
+        m_sampleVersion = 0;
+      }
+    };
+    struct Attr {
+      Uint32* m_attr;
+      Uint32 m_attrMax;
+      Uint32 m_attrSize;
+      Uint32* m_data;
+      Uint32 m_dataMax;
+      Uint32 m_dataSize;
+      Attr() {}
+    };
+    struct Util {
+      Uint32 m_prepareId;
+      bool m_not_found;
+      Callback m_cb;
+      Util() {
+        m_prepareId = RNIL;
+        m_not_found = false; // read + ZNOT_FOUND
+      };
+    };
+    struct Clean {
+      Uint32 m_cleanCount;
+      // bounds on index_id, index_version, sample_version
+      Uint32 m_bound[3 * 3];
+      Uint32 m_boundCount;
+      Uint32 m_boundSize;
+      Clean() {}
+    };
+    struct Scan {
+      Uint32 m_sampleCount;
+      Uint32 m_keyBytes;
+      Scan() {}
+    };
+    struct Drop {
+    };
+    struct Send {
+      const SysTable* m_sysTable;
+      Uint32 m_operationType;     // UtilPrepareReq::OperationTypeValue
+      Uint32 m_prepareId;
+      Send() {}
+    };
+    IndexStatImplReq m_req;
+    Uint32 m_requestType;
+    const char* m_requestName;
+    Uint32 m_subRecPtrI;
+    Meta m_meta;
+    Data m_data;
+    Attr m_attr;
+    Util m_util;
+    Clean m_clean;
+    Scan m_scan;
+    Drop m_drop;
+    Send m_send;
+    Uint32 m_errorCode;
+    Uint32 m_errorLine;
+    union {
+      Uint32 m_ownPtrI;
+      Uint32 nextPool;
+    };
+    StatOp() {
+      m_subRecPtrI = RNIL;
+      m_errorCode = 0;
+      m_errorLine = 0;
+    };
+  };
+  typedef Ptr<StatOp> StatOpPtr;
+  ArrayPool<StatOp> c_statOpPool;
+  RSS_AP_SNAPSHOT(c_statOpPool);
+
   // System start
   void execREAD_CONFIG_REQ(Signal* signal);
   void execSTTOR(Signal* signal);
@@ -208,6 +338,83 @@ private:
 		   SubscriptionRecPtr subRecPtr, 
 		   BuildIndxRef::ErrorCode);
   void checkParallelism(Signal* signal, SubscriptionRecord* subRec);
+
+  // index stats
+  StatOp& statOpGetPtr(Uint32 statPtrI);
+  bool statOpSeize(Uint32& statPtrI);
+  void statOpRelease(StatOp&);
+  void execINDEX_STAT_IMPL_REQ(Signal*);
+  // sys tables metadata
+  void statMetaGetHead(Signal*, StatOp&);
+  void statMetaGetHeadCB(Signal*, Uint32 statPtrI, Uint32 ret);
+  void statMetaGetSample(Signal*, StatOp&);
+  void statMetaGetSampleCB(Signal*, Uint32 statPtrI, Uint32 ret);
+  void statMetaGetSampleX1(Signal*, StatOp&);
+  void statMetaGetSampleX1CB(Signal*, Uint32 statPtrI, Uint32 ret);
+  void sendGetTabInfoReq(Signal*, StatOp&, const char* name);
+  void execGET_TABINFO_CONF(Signal*);
+  void execGET_TABINFO_REF(Signal*);
+  // continue
+  void statGetMetaDone(Signal*, StatOp&);
+  // head table ops
+  void statHeadRead(Signal*, StatOp&);
+  void statHeadReadCB(Signal*, Uint32 statPtrI, Uint32 ret);
+  void statHeadInsert(Signal*, StatOp&);
+  void statHeadInsertCB(Signal*, Uint32 statPtrI, Uint32 ret);
+  void statHeadUpdate(Signal*, StatOp&);
+  void statHeadUpdateCB(Signal*, Uint32 statPtrI, Uint32 ret);
+  void statHeadDelete(Signal*, StatOp&);
+  void statHeadDeleteCB(Signal*, Uint32 statPtrI, Uint32 ret);
+  // util
+  void statUtilPrepare(Signal*, StatOp&);
+  void statUtilPrepareConf(Signal*, Uint32 statPtrI);
+  void statUtilPrepareRef(Signal*, Uint32 statPtrI);
+  void statUtilExecute(Signal*, StatOp&);
+  void statUtilExecuteConf(Signal*, Uint32 statPtrI);
+  void statUtilExecuteRef(Signal*, Uint32 statPtrI);
+  void statUtilRelease(Signal*, StatOp&);
+  void statUtilReleaseConf(Signal*, Uint32 statPtrI);
+  // continue
+  void statReadHeadDone(Signal*, StatOp&);
+  void statInsertHeadDone(Signal*, StatOp&);
+  void statUpdateHeadDone(Signal*, StatOp&);
+  void statDeleteHeadDone(Signal*, StatOp&);
+  // clean
+  void statCleanBegin(Signal*, StatOp&);
+  void statCleanPrepare(Signal*, StatOp&);
+  void statCleanExecute(Signal*, StatOp&);
+  void statCleanRelease(Signal*, StatOp&);
+  void statCleanEnd(Signal*, StatOp&);
+  // scan
+  void statScanBegin(Signal*, StatOp&);
+  void statScanPrepare(Signal*, StatOp&);
+  void statScanExecute(Signal*, StatOp&);
+  void statScanRelease(Signal*, StatOp&);
+  void statScanEnd(Signal*, StatOp&);
+  // drop
+  void statDropBegin(Signal*, StatOp&);
+  void statDropEnd(Signal*, StatOp&);
+  // send
+  void statSendPrepare(Signal*, StatOp&);
+  void statSendExecute(Signal*, StatOp&);
+  void statSendRelease(Signal*, StatOp&);
+  // data
+  void statDataPtr(StatOp&, Uint32 i, Uint32*& dptr, Uint32& bytes);
+  void statDataOut(StatOp&, Uint32 i);
+  void statDataIn(StatOp&, Uint32 i);
+  // abort ongoing
+  void statAbortUtil(Signal*, StatOp&);
+  void statAbortUtilCB(Signal*, Uint32 statPtrI, Uint32 ret);
+  // conf and ref
+  void statOpSuccess(Signal*, StatOp&);
+  void statOpConf(Signal*, StatOp&);
+  void statOpError(Signal*, StatOp&, Uint32 errorCode, Uint32 errorLine);
+  void statOpAbort(Signal*, StatOp&);
+  void statOpRef(Signal*, StatOp&);
+  void statOpRef(Signal*, const IndexStatImplReq*, Uint32 errorCode, Uint32 errorLine);
+  void statOpEvent(StatOp&, const char* level, const char* msg, ...);
+  // debug
+  friend class NdbOut& operator<<(NdbOut&, const StatOp& stat);
 };
 
 #endif

=== modified file 'storage/ndb/src/mgmsrv/ConfigInfo.cpp'
--- a/storage/ndb/src/mgmsrv/ConfigInfo.cpp	2011-04-19 13:47:33 +0000
+++ b/storage/ndb/src/mgmsrv/ConfigInfo.cpp	2011-05-19 09:16:32 +0000
@@ -1985,6 +1985,114 @@ const ConfigInfo::ParamInfo ConfigInfo::
     STR_VALUE(MAX_INT_RNIL)      /* Max */
   },
 
+  /* ordered index stats */
+
+  {
+    CFG_DB_INDEX_STAT_AUTO_CREATE,
+    "IndexStatAutoCreate",
+    DB_TOKEN,
+    "Make create index also create initial index stats",
+    ConfigInfo::CI_USED,
+    0,
+    ConfigInfo::CI_INT,
+    "0",
+    "0",
+    "1"
+  },
+
+  {
+    CFG_DB_INDEX_STAT_AUTO_UPDATE,
+    "IndexStatAutoUpdate",
+    DB_TOKEN,
+    "Monitor each index for changes and trigger automatic stats updates."
+    " See IndexStatTrigger options",
+    ConfigInfo::CI_USED,
+    0,
+    ConfigInfo::CI_INT,
+    "0",
+    "0",
+    "1"
+  },
+
+  {
+    CFG_DB_INDEX_STAT_SAVE_SIZE,
+    "IndexStatSaveSize",
+    DB_TOKEN,
+    "Maximum bytes allowed for the saved stats of one index."
+    " At least 1 sample is produced regardless of size limit."
+    " The size is scaled up by a factor from IndexStatSaveScale."
+    " The value affects size of stats saved in NDB system tables"
+    " and in mysqld memory cache",
+    ConfigInfo::CI_USED,
+    0,
+    ConfigInfo::CI_INT,
+    "32768",
+    "0",
+    STR_VALUE(MAX_INT_RNIL)
+  },
+
+  {
+    CFG_DB_INDEX_STAT_SAVE_SCALE,
+    "IndexStatSaveScale",
+    DB_TOKEN,
+    "Factor to scale up IndexStatSaveSize for a large index."
+    " Given in units of 0.01."
+    " Multiplied by a logarithmic index size."
+    " Value 0 disables scaling",
+    ConfigInfo::CI_USED,
+    0,
+    ConfigInfo::CI_INT,
+    "100",
+    "0",
+    STR_VALUE(MAX_INT_RNIL)
+  },
+
+  {
+    CFG_DB_INDEX_STAT_TRIGGER_PCT,
+    "IndexStatTriggerPct",
+    DB_TOKEN,
+    "Percent change (in DML ops) to schedule index stats update."
+    " The value is scaled down by a factor from IndexStatTriggerScale."
+    " Value 0 disables the trigger",
+    ConfigInfo::CI_USED,
+    0,
+    ConfigInfo::CI_INT,
+    "100",
+    "0",
+    STR_VALUE(MAX_INT_RNIL)
+  },
+
+  {
+    CFG_DB_INDEX_STAT_TRIGGER_SCALE,
+    "IndexStatTriggerScale",
+    DB_TOKEN,
+    "Factor to scale down IndexStatTriggerPct for a large index."
+    " Given in units of 0.01."
+    " Multiplied by a logarithmic index size."
+    " Value 0 disables scaling",
+    ConfigInfo::CI_USED,
+    0,
+    ConfigInfo::CI_INT,
+    "100",
+    "0",
+    STR_VALUE(MAX_INT_RNIL)
+  },
+
+  {
+    CFG_DB_INDEX_STAT_UPDATE_DELAY,
+    "IndexStatUpdateDelay",
+    DB_TOKEN,
+    "Minimum delay in seconds between automatic index stats updates"
+    " for a given index."
+    " Value 0 means no delay",
+    ConfigInfo::CI_USED,
+    0,
+    ConfigInfo::CI_INT,
+    "60",
+    "0",
+    STR_VALUE(MAX_INT_RNIL)
+  },
+
   /***************************************************************************
    * API
    ***************************************************************************/

=== modified file 'storage/ndb/src/ndbapi/ndberror.c'
--- a/storage/ndb/src/ndbapi/ndberror.c	2011-05-25 13:19:02 +0000
+++ b/storage/ndb/src/ndbapi/ndberror.c	2011-05-31 08:28:58 +0000
@@ -449,6 +449,13 @@ ErrorBundle ErrorCodes[] = {
   { 908,  DMEC, IS, "Invalid ordered index tree node size" },
   { 909,  DMEC, IE, "No free index scan op" },
   { 910, HA_ERR_NO_SUCH_TABLE, SE, "Index is being dropped" },
+  { 913,  DMEC, AE, "Invalid index for index state update" },
+  { 914,  DMEC, IE, "Invalid index stats request" },
+  { 915,  DMEC, TR, "No free index stats op" },
+  { 916,  DMEC, IE, "Invalid index stats sys tables" },
+  { 917,  DMEC, IE, "Invalid index stats sys tables data" },
+  { 918,  DMEC, TR, "Cannot prepare index stats update" },
+  { 919,  DMEC, TR, "Cannot execute index stats update" },
   { 1224, HA_WRONG_CREATE_OPTION, SE, "Too many fragments" },
   { 1225, DMEC, SE, "Table not defined in local query handler" },
   { 1226, DMEC, SE, "Table is being dropped" },
@@ -529,6 +536,16 @@ ErrorBundle ErrorCodes[] = {
   { 1702, DMEC, AE, "Node already connected" },
   { 1703, DMEC, IT, "Node failure handling not completed" },
   { 1704, DMEC, AE, "Node type mismatch" },
+
+  /*
+   * Index stats error codes
+   */
+  { 4714, DMEC, AE, "Index stats sys tables " NDB_INDEX_STAT_PREFIX " are invalid" },
+  { 4715, DMEC, AE, "Index stats for specified index do not exist" },
+  { 4716, DMEC, AE, "Index stats methods usage error" },
+  { 4717, DMEC, AE, "Index stats cannot allocate memory" },
+  { 4718, DMEC, IE, "Index stats memory cache is corrupted" },
+  { 4719, DMEC, IE, "Index stats internal error" },
   
   /**
    * Still uncategorized
@@ -554,6 +571,7 @@ ErrorBundle ErrorCodes[] = {
   { 1425, DMEC, SE, "Subscription being defined...while trying to stop subscriber" },
   { 1426, DMEC, SE, "No such subscriber" },
   { 1427, DMEC, NR, "Api node died, when SUB_START_REQ reached node "},
+  { 1428, DMEC, IE, "No replica to scan on this node (internal index stats error)" },
 
   { 4004, DMEC, AE, "Attribute name or id not found in the table" },
   

=== modified file 'storage/ndb/tools/restore/Restore.cpp'
--- a/storage/ndb/tools/restore/Restore.cpp	2011-04-04 09:41:30 +0000
+++ b/storage/ndb/tools/restore/Restore.cpp	2011-05-31 08:28:58 +0000
@@ -523,6 +523,10 @@ RestoreMetaData::markSysTables()
         strcmp(tableName, "NDB$EVENTS_0") == 0 ||
         strcmp(tableName, "sys/def/SYSTAB_0") == 0 ||
         strcmp(tableName, "sys/def/NDB$EVENTS_0") == 0 ||
+        // index stats tables and indexes
+        strncmp(tableName, NDB_INDEX_STAT_PREFIX,
+                sizeof(NDB_INDEX_STAT_PREFIX)-1) == 0 ||
+        strstr(tableName, "/" NDB_INDEX_STAT_PREFIX) != 0 ||
         /*
           The following is for old MySQL versions,
            before we changed the database name of the tables from

No bundle (reason: useless for push emails).
Thread
bzr push into mysql-5.1-telco-7.0 branch (jonas:4427 to 4428) Jonas Oreland31 May