MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
ha_ndbcluster_push.cc
Go to the documentation of this file.
1 /*
2  Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation; version 2 of the License.
7 
8  This program is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12 
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
16 */
26 #include "ha_ndbcluster_glue.h"
27 
28 #ifdef WITH_NDBCLUSTER_STORAGE_ENGINE
29 
30 #include "ha_ndbcluster.h"
31 #include "ha_ndbcluster_push.h"
32 #include "ha_ndbcluster_binlog.h"
33 #include "ha_ndbcluster_cond.h"
34 #include "abstract_query_plan.h"
35 
36 #include <ndbapi/NdbApi.hpp>
37 #include <ndbapi/NdbInterpretedCode.hpp>
38 #include "../storage/ndb/src/ndbapi/NdbQueryBuilder.hpp"
39 #include "../storage/ndb/src/ndbapi/NdbQueryOperation.hpp"
40 
41 #include <ndb_version.h>
42 
43 #define EXPLAIN_NO_PUSH(msgfmt, ...) \
44 do \
45 { \
46  if (unlikely(current_thd->lex->describe & DESCRIBE_EXTENDED)) \
47  { \
48  ndbcluster_explain_no_push ((msgfmt), __VA_ARGS__); \
49  } \
50 } \
51 while(0)
52 
53 
54 static inline const char* get_referred_field_name(const Item_field* field_item)
55 {
56  DBUG_ASSERT(field_item->type() == Item::FIELD_ITEM);
57  return field_item->field->field_name;
58 }
59 
60 static const char* get_referred_table_access_name(const Item_field* field_item)
61 {
62  DBUG_ASSERT(field_item->type() == Item::FIELD_ITEM);
63  return field_item->field->table->alias;
64 }
65 
66 static bool ndbcluster_is_lookup_operation(AQP::enum_access_type accessType)
67 {
68  return accessType == AQP::AT_PRIMARY_KEY ||
69  accessType == AQP::AT_MULTI_PRIMARY_KEY ||
70  accessType == AQP::AT_UNIQUE_KEY;
71 }
72 
77 static void ndbcluster_explain_no_push(const char* msgfmt, ...)
78 {
79  va_list args;
80  char wbuff[1024];
81  va_start(args,msgfmt);
82  (void) my_vsnprintf (wbuff, sizeof(wbuff), msgfmt, args);
83  va_end(args);
90  uint warn_code= 9999;
91  push_warning(current_thd, Sql_condition::WARN_LEVEL_NOTE, warn_code,
92  wbuff);
93 } // ndbcluster_explain_no_push();
94 
95 
96 uint
97 ndb_table_access_map::first_table(uint start) const
98 {
99  for (uint table_no= start; table_no<length(); table_no++)
100  {
101  if (contain(table_no))
102  return table_no;
103  }
104  return length();
105 }
106 
107 uint
108 ndb_table_access_map::last_table(uint start) const
109 {
110  uint table_no= start;
111  while(true)
112  {
113  if (contain(table_no))
114  return table_no;
115  else if (table_no == 0)
116  return length();
117  table_no--;
118  }
119 }
120 
121 ndb_pushed_join::ndb_pushed_join(
122  const ndb_pushed_builder_ctx& builder,
123  const NdbQueryDef* query_def)
124 :
125  m_query_def(query_def),
126  m_operation_count(0),
127  m_field_count(builder.m_fld_refs)
128 {
129  DBUG_ASSERT(query_def != NULL);
130  DBUG_ASSERT(builder.m_fld_refs <= MAX_REFERRED_FIELDS);
131  ndb_table_access_map searched;
132  for (uint tab_no= 0; !(searched==builder.m_join_scope); tab_no++)
133  {
134  const AQP::Table_access* const join_tab= builder.m_plan.get_table_access(tab_no);
135  if (builder.m_join_scope.contain(tab_no))
136  {
137  DBUG_ASSERT(m_operation_count < MAX_PUSHED_OPERATIONS);
138  m_tables[m_operation_count++] = join_tab->get_table();
139  searched.add(tab_no);
140  }
141  }
142  for (uint i= 0; i < builder.m_fld_refs; i++)
143  {
144  m_referred_fields[i] = builder.m_referred_fields[i];
145  }
146 }
147 
148 ndb_pushed_join::~ndb_pushed_join()
149 {
150  if (m_query_def)
151  m_query_def->destroy();
152 }
153 
155  int type, //NdbQueryOperationDef::Type,
156  const NDB_INDEX_DATA* idx,
157  bool needSorted) const
158 {
159  const NdbQueryOperationDef* const root_operation=
160  m_query_def->getQueryOperation((uint)0);
161  const NdbQueryOperationDef::Type def_type=
162  root_operation->getType();
163 
164  if (def_type != type)
165  {
166  DBUG_PRINT("info",
167  ("Cannot execute push join. Root operation prepared as %s "
168  "not executable as %s",
169  NdbQueryOperationDef::getTypeName(def_type),
170  NdbQueryOperationDef::getTypeName((NdbQueryOperationDef::Type)type)));
171  return FALSE;
172  }
173  const NdbDictionary::Index* const expected_index= root_operation->getIndex();
174 
175  // Check that we still use the same index as when the query was prepared.
176  switch (def_type)
177  {
179  DBUG_ASSERT(idx!=NULL);
180  DBUG_ASSERT(idx->unique_index == expected_index);
181  break;
182 
184  DBUG_ASSERT(idx!=NULL);
185  // DBUG_ASSERT(idx->unique_index == expected_index);
186  if (idx->unique_index != expected_index)
187  {
188  DBUG_PRINT("info", ("Actual index %s differs from expected index %s."
189  "Therefore, join cannot be pushed.",
190  idx->unique_index->getName(),
191  expected_index->getName()));
192  return FALSE;
193  }
194  break;
195 
197  DBUG_ASSERT (idx==NULL && expected_index==NULL);
198  if (needSorted)
199  {
200  DBUG_PRINT("info",
201  ("TableScan access can not be provied as sorted result. "
202  "Therefore, join cannot be pushed."));
203  return FALSE;
204  }
205  break;
206 
208  DBUG_ASSERT(idx!=NULL);
209  // DBUG_ASSERT(idx->index == expected_index);
210  if (idx->index != expected_index)
211  {
212  DBUG_PRINT("info", ("Actual index %s differs from expected index %s. "
213  "Therefore, join cannot be pushed.",
214  idx->index->getName(),
215  expected_index->getName()));
216  return FALSE;
217  }
218  if (needSorted && m_query_def->getQueryType() == NdbQueryDef::MultiScanQuery)
219  {
220  DBUG_PRINT("info",
221  ("OrderedIndexScan with scan siblings "
222  "can not execute as pushed join."));
223  return FALSE;
224  }
225  break;
226 
227  default:
228  DBUG_ASSERT(false);
229  break;
230  }
231 
237  for (uint i= 0; i < get_field_referrences_count(); i++)
238  {
239  Field* field= m_referred_fields[i];
240  if (field->is_real_null())
241  {
242  DBUG_PRINT("info",
243  ("paramValue is NULL, can not execute as pushed join"));
244  return FALSE;
245  }
246  }
247 
248  return TRUE;
249 }
250 
252  NdbTransaction* trans,
253  const NdbQueryParamValue* keyFieldParams,
254  uint paramCnt) const
255 {
256  DBUG_ENTER("make_query_instance");
257  DBUG_PRINT("info",
258  ("executing chain of %d pushed joins."
259  " First table is %s, accessed as %s.",
261  get_table(0)->alias,
262  NdbQueryOperationDef::getTypeName(
263  m_query_def->getQueryOperation((uint)0)->getType())
264  ));
265 
266  const NdbQueryParamValue* paramValues = keyFieldParams;
267 
273  uint outer_fields= get_field_referrences_count();
274  NdbQueryParamValue* extendedParams = NULL;
275  if (unlikely(outer_fields > 0))
276  {
277  uint size= sizeof(NdbQueryParamValue) * (paramCnt+outer_fields);
278  extendedParams = reinterpret_cast<NdbQueryParamValue*>(my_alloca(size));
279  // Copy specified keyFieldParams[] first
280  for (uint i= 0; i < paramCnt; i++)
281  {
282  new (extendedParams + i) NdbQueryParamValue(keyFieldParams[i]);
283  }
284 
285  // There may be referrences to Field values from tables outside the scope of
286  // our pushed join: These are expected to be supplied as paramValues()
287  for (uint i= 0; i < outer_fields; i++)
288  {
289  Field* field= m_referred_fields[i];
290  DBUG_ASSERT(!field->is_real_null()); // Checked by ::check_if_pushable()
291  new (extendedParams + paramCnt + i) NdbQueryParamValue(field->ptr, false);
292  }
293  paramValues= extendedParams;
294  }
295 
296  NdbQuery* query= trans->createQuery(&get_query_def(), paramValues);
297  if (unlikely(extendedParams != NULL))
298  {
299  for (uint i = 0; i < paramCnt + outer_fields; i++)
300  {
301  extendedParams[i].~NdbQueryParamValue();
302  }
303  my_afree(extendedParams);
304  }
305  DBUG_RETURN(query);
306 }
307 
309 
310 ndb_pushed_builder_ctx::ndb_pushed_builder_ctx(const AQP::Join_plan& plan)
311 :
312  m_plan(plan),
313  m_join_root(),
314  m_join_scope(),
315  m_const_scope(),
316  m_fld_refs(0),
317  m_builder(NULL)
318 {
319  const uint count= m_plan.get_access_count();
320  (void)ha_ndb_ext; // Prevents compiler warning.
321 
322  DBUG_ASSERT(count <= MAX_TABLES);
323  if (count > 1)
324  {
325  for (uint i= 0; i < count; i++)
326  {
327  m_tables[i].m_maybe_pushable= 0;
328 
329  const AQP::Table_access* const table = m_plan.get_table_access(i);
330  if (table->get_table()->file->ht != ndbcluster_hton)
331  {
332  EXPLAIN_NO_PUSH("Table '%s' not in ndb engine, not pushable",
333  table->get_table()->alias);
334  continue;
335  }
336 
337  switch (table->get_access_type())
338  {
339  case AQP::AT_VOID:
340  DBUG_ASSERT(false);
341  break;
342 
343  case AQP::AT_FIXED:
344  EXPLAIN_NO_PUSH("Table '%s' was optimized away, or const'ified by optimizer",
345  table->get_table()->alias);
346  break;
347 
348  case AQP::AT_OTHER:
349  EXPLAIN_NO_PUSH("Table '%s' is not pushable: %s",
350  table->get_table()->alias,
351  table->get_other_access_reason());
352  break;
353 
354  case AQP::AT_UNDECIDED:
355  EXPLAIN_NO_PUSH("Table '%s' is not pushable: "
356  "Access type was not chosen at 'prepare' time",
357  table->get_table()->alias);
358  break;
359 
360  default:
361  const char* reason= NULL;
362  const ha_ndbcluster* handler=
363  static_cast<ha_ndbcluster*>(table->get_table()->file);
364 
365  if (handler->maybe_pushable_join(reason))
366  {
367  m_tables[i].m_maybe_pushable= PUSHABLE_AS_CHILD | PUSHABLE_AS_PARENT;
368  }
369  else
370  {
371  EXPLAIN_NO_PUSH("Table '%s' is not pushable: %s",
372  table->get_table()->alias, reason);
373  }
374  break;
375  }
376  }
377 
378  m_tables[0].m_maybe_pushable &= ~PUSHABLE_AS_CHILD;
379  m_tables[count-1].m_maybe_pushable &= ~PUSHABLE_AS_PARENT;
380 
381  // Fill in table for maping internal <-> external table enumeration
382  for (uint i= 0; i < count; i++)
383  {
384  const AQP::Table_access* const table = m_plan.get_table_access(i);
385  uint external= table->get_table()->tablenr;
386  DBUG_ASSERT(external <= MAX_TABLES);
387 
388  m_remap[i].to_external= external;
389  m_remap[external].to_internal= i;
390  }
391  }
392 } // ndb_pushed_builder_ctx::ndb_pushed_builder_ctx()
393 
394 ndb_pushed_builder_ctx::~ndb_pushed_builder_ctx()
395 {
396  if (m_builder)
397  {
398  m_builder->destroy();
399  }
400 }
401 
402 const NdbError& ndb_pushed_builder_ctx::getNdbError() const
403 {
404  DBUG_ASSERT(m_builder);
405  return m_builder->getNdbError();
406 }
407 
411 uint
412 ndb_pushed_builder_ctx::get_table_no(const Item* key_item) const
413 {
414  DBUG_ASSERT(key_item->type() == Item::FIELD_ITEM);
415  table_map bitmap= key_item->used_tables();
416 
417  for (uint i= 0; i<MAX_TABLES && bitmap!=0; i++, bitmap>>=1)
418  {
419  if (bitmap & 1)
420  {
421  DBUG_ASSERT(bitmap == 0x01); // Only a single table in 'bitmap'
422  return m_remap[i].to_internal;
423  }
424  }
425  return MAX_TABLES;
426 }
427 
450 int
452  const AQP::Table_access* join_root,
453  const ndb_pushed_join* &pushed_join)
454 {
455  DBUG_ENTER("make_pushed_join");
456  pushed_join= NULL;
457 
458  if (is_pushable_with_root(join_root))
459  {
460  int error;
461  error= optimize_query_plan();
462  if (unlikely(error))
463  DBUG_RETURN(error);
464 
465  error= build_query();
466  if (unlikely(error))
467  DBUG_RETURN(error);
468 
469  const NdbQueryDef* const query_def= m_builder->prepare();
470  if (unlikely(query_def == NULL))
471  DBUG_RETURN(-1); // Get error with ::getNdbError()
472 
473  pushed_join= new ndb_pushed_join(*this, query_def);
474  if (unlikely (pushed_join == NULL))
475  DBUG_RETURN(HA_ERR_OUT_OF_MEM);
476 
477  DBUG_PRINT("info", ("Created pushed join with %d child operations",
478  pushed_join->get_operation_count()-1));
479  }
480  DBUG_RETURN(0);
481 } // ndb_pushed_builder_ctx::make_pushed_join()
482 
483 
489 bool
490 ndb_pushed_builder_ctx::is_pushable_with_root(const AQP::Table_access* root)
491 {
492  DBUG_ENTER("is_pushable_with_root");
493 
494  const uint root_no= root->get_access_no();
495  if ((m_tables[root_no].m_maybe_pushable & PUSHABLE_AS_PARENT) != PUSHABLE_AS_PARENT)
496  {
497  DBUG_PRINT("info", ("Table %d already reported 'not pushable_as_parent'", root_no));
498  DBUG_RETURN(false);
499  }
500 
501  const AQP::enum_access_type access_type= root->get_access_type();
502  DBUG_ASSERT(access_type != AQP::AT_VOID);
503 
504  if (access_type == AQP::AT_MULTI_UNIQUE_KEY)
505  {
506  EXPLAIN_NO_PUSH("Table '%s' is not pushable, "
507  "access type 'MULTI_UNIQUE_KEY' not implemented",
508  root->get_table()->alias);
509  m_tables[root_no].m_maybe_pushable &= ~PUSHABLE_AS_PARENT;
510  DBUG_RETURN(false);
511  }
512 
517  DBUG_PRINT("info", ("Table %d is pushable as root", root->get_access_no()));
518  DBUG_EXECUTE("info", root->dbug_print(););
519  m_fld_refs= 0;
520  m_join_root= root;
521  m_const_scope.set_prefix(root_no);
522  m_join_scope= ndb_table_access_map(root_no);
523 
524  uint push_cnt= 0;
525  for (uint tab_no= root->get_access_no()+1; tab_no<m_plan.get_access_count(); tab_no++)
526  {
527  const AQP::Table_access* const table= m_plan.get_table_access(tab_no);
528  if (is_pushable_as_child(table))
529  {
530  push_cnt++;
531  }
532  }
533  DBUG_RETURN(push_cnt>0);
534 
535 } // ndb_pushed_builder_ctx::is_pushable_with_root()
536 
537 
538 /***************************************************************
539  * is_pushable_as_child()
540  *
541  * Determines if the specified child ('table') can be appended to
542  * an existing chain of previously pushed join operations.
543  *
544  * To be considdered pushable the child operation should:
545  *
546  * 1) Have an REF to the previous parent operations.
547  * 2) Refer only a single parent, or a grandparent reachable through
548  * a single parent common to all key fields in the 'REF'
549  *
550  * In order to increase pushability we use the COND_EQUAL sets
551  * to resolve cases (2) above) where multiple parents are refered.
552  * If needed too make a child pushable, we replace parent
553  * references with another from the COND_EQUAL sets which make
554  * it pushable .
555  ****************************************************************/
556 bool
557 ndb_pushed_builder_ctx::is_pushable_as_child(
558  const AQP::Table_access* table)
559 {
560  DBUG_ENTER("is_pushable_as_child");
561  const uint root_no= m_join_root->get_access_no();
562  const uint tab_no= table->get_access_no();
563 
564  DBUG_ASSERT(tab_no > root_no);
565 
566  if ((m_tables[tab_no].m_maybe_pushable & PUSHABLE_AS_CHILD) != PUSHABLE_AS_CHILD)
567  {
568  DBUG_PRINT("info", ("Table %s already known 'not is_pushable_as_child'", table->get_table()->alias));
569  DBUG_RETURN(false);
570  }
571 
572  const AQP::enum_access_type root_type= m_join_root->get_access_type();
573  const AQP::enum_access_type access_type= table->get_access_type();
574 
575  if (!(ndbcluster_is_lookup_operation(access_type) ||
576  access_type==AQP::AT_ORDERED_INDEX_SCAN))
577  {
578  EXPLAIN_NO_PUSH("Can't push table '%s' as child, 'type' must be a 'ref' access",
579  table->get_table()->alias);
580  m_tables[tab_no].m_maybe_pushable &= ~PUSHABLE_AS_CHILD;
581  DBUG_RETURN(false);
582  }
583 
584  // Currently there is a limitation in not allowing LOOKUP - (index)SCAN operations
585  if (access_type==AQP::AT_ORDERED_INDEX_SCAN &&
586  ndbcluster_is_lookup_operation(root_type))
587  {
588  EXPLAIN_NO_PUSH("Push of table '%s' as scan-child "
589  "with lookup-root '%s' not implemented",
590  table->get_table()->alias,
591  m_join_root->get_table()->alias);
592  // 'table' may still be PUSHABLE_AS_CHILD with another parent
593  DBUG_RETURN(false);
594  }
595 
596  if (access_type==AQP::AT_ORDERED_INDEX_SCAN && m_join_root->is_fixed_ordered_index())
597  {
598  // root must be an ordered index scan - Thus it cannot have other scan descendant.
599  EXPLAIN_NO_PUSH("Push of table '%s' as scan-child "
600  "with ordered indexscan-root '%s' not implemented",
601  table->get_table()->alias,
602  m_join_root->get_table()->alias);
603  DBUG_RETURN(false);
604  }
605 
607  {
608  EXPLAIN_NO_PUSH("Can't push table '%s' as child, "
609  "to many ref'ed parent fields",
610  table->get_table()->alias);
611  m_tables[tab_no].m_maybe_pushable &= ~PUSHABLE_AS_CHILD; // Permanently dissable
612  DBUG_RETURN(false);
613  }
614 
615  for (uint i = tab_no - 1; i >= root_no && i < ~uint(0);
616  i--)
617  {
618  if (m_plan.get_table_access(i)->uses_join_cache())
619  {
620  EXPLAIN_NO_PUSH("Cannot push table '%s' as child of table '%s'. Doing so "
621  "would prevent using join buffer for table '%s'.",
622  table->get_table()->alias,
623  m_join_root->get_table()->alias,
624  m_plan.get_table_access(i+1)->get_table()->alias);
625  DBUG_RETURN(false);
626  }
627  }
628 
629  DBUG_PRINT("info", ("Table:%d, Checking %d REF keys", tab_no,
630  table->get_no_of_key_fields()));
631 
632  /*****
633  * Calculate the set of possible parents for table, where:
634  * - 'current' are those currently being referred by the
635  * FIELD_ITEMs as set up by the MySQL optimizer.
636  * - 'common' are those we may refer (possibly through the EQ-sets)
637  * such that all FIELD_ITEMs are from the same parent.
638  * - 'extended' are those parents refered from some of the
639  * FIELD_ITEMs, and having the rest of the referred FIELD_ITEM
640  * tables available as 'grandparent refs'
641  * (The SPJ block can handle field references to any ancestor
642  * operation, not just the (direct) parent).
643  *
644  * In addition there are firm dependencies between some parents
645  * such that all 'depend_parents' must be referred as an ancestors
646  * of the table. By default 'depend_parents' will at least contain
647  * the most 'grandparent' of the extended parents.
648  *
649  ****/
650  ndb_table_access_map current_parents;
651  ndb_table_access_map common_parents(m_join_scope);
652  ndb_table_access_map extend_parents;
653  ndb_table_access_map depend_parents;
654 
655  for (uint key_part_no= 0;
656  key_part_no < table->get_no_of_key_fields();
657  key_part_no++)
658  {
659  const Item* const key_item= table->get_key_field(key_part_no);
660  const KEY_PART_INFO* key_part= table->get_key_part_info(key_part_no);
661 
662  if (key_item->const_item()) // REF is a litteral or field from const-table
663  {
664  DBUG_PRINT("info", (" Item type:%d is 'const_item'", key_item->type()));
665  if (!is_const_item_pushable(key_item, key_part))
666  {
667  DBUG_RETURN(false);
668  }
669  }
670  else if (key_item->type() == Item::FIELD_ITEM)
671  {
676  ndb_table_access_map field_parents;
677  if (!is_field_item_pushable(table, key_item, key_part, field_parents))
678  {
679  DBUG_RETURN(false);
680  }
681 
682  if (key_item->type() == Item::FIELD_ITEM)
683  {
684  uint referred_table_no= get_table_no(key_item);
685  current_parents.add(referred_table_no);
686  }
687 
692  common_parents.intersect(field_parents);
693 
702  extend_parents.add(field_parents);
703 
704  const uint first= field_parents.first_table(root_no);
705  depend_parents.add(first);
706  }
707  else
708  {
709  EXPLAIN_NO_PUSH("Can't push table '%s' as child, "
710  "column '%s' does neither 'ref' a column nor a constant",
711  table->get_table()->alias,
712  key_part->field->field_name);
713  m_tables[tab_no].m_maybe_pushable &= ~PUSHABLE_AS_CHILD; // Permanently disable as child
714  DBUG_RETURN(false);
715  }
716  } // for (uint key_part_no= 0 ...
717 
718  if (m_const_scope.contain(current_parents))
719  {
720  // NOTE: This is a constant table wrt. this instance of the pushed join.
721  // It should be relatively simple to extend the SPJ block to
722  // allow such tables to be included in the pushed join.
723  EXPLAIN_NO_PUSH("Can't push table '%s' as child of '%s', "
724  "their dependency is 'const'",
725  table->get_table()->alias,
726  m_join_root->get_table()->alias);
727  DBUG_RETURN(false);
728  }
729  else if (extend_parents.is_clear_all())
730  {
731  EXPLAIN_NO_PUSH("Can't push table '%s' as child of '%s', "
732  "no parents found within scope",
733  table->get_table()->alias,
734  m_join_root->get_table()->alias);
735  DBUG_RETURN(false);
736  }
737 
738  if (!ndbcluster_is_lookup_operation(table->get_access_type()))
739  {
767  if (table->get_join_type(m_join_root) == AQP::JT_OUTER_JOIN)
768  {
769  EXPLAIN_NO_PUSH("Can't push table '%s' as child of '%s', "
770  "outer join of scan-child not implemented",
771  table->get_table()->alias,
772  m_join_root->get_table()->alias);
773  DBUG_RETURN(false);
774  }
775  } // scan operation
776 
781  ndb_table_access_map grandparents(depend_parents);
782  grandparents.clear_bit(root_no);
783  uint ancestor_no= root_no+1;
784  while (!grandparents.is_clear_all())
785  {
786  if (grandparents.contain(ancestor_no))
787  {
788  grandparents.clear_bit(ancestor_no);
789  if (grandparents.is_clear_all())
790  break; // done
791 
792  const AQP::Table_access* const ancestor=
793  m_plan.get_table_access(ancestor_no);
794 
795  if (ancestor->get_join_type(m_join_root) == AQP::JT_OUTER_JOIN)
796  {
797  EXPLAIN_NO_PUSH("Can't push table '%s' as child of '%s', "
798  "dependencies on outer joined grandparents not implemented",
799  table->get_table()->alias,
800  m_join_root->get_table()->alias);
801  DBUG_RETURN(false);
802  }
803  }
804  ancestor_no++;
805  }
806 
807  DBUG_ASSERT(m_join_scope.contain(common_parents));
808  DBUG_ASSERT(m_join_scope.contain(extend_parents));
809  DBUG_ASSERT(extend_parents.is_clear_all() ||
810  extend_parents.contain(common_parents));
814  m_tables[tab_no].m_common_parents= common_parents;
815  m_tables[tab_no].m_extend_parents= extend_parents;
816  m_tables[tab_no].m_depend_parents= depend_parents;
817  m_tables[tab_no].m_parent= MAX_TABLES;
818 
819  m_tables[tab_no].m_maybe_pushable= 0; // Exclude from further pushing
820  m_join_scope.add(tab_no);
821 
822  DBUG_RETURN(true);
823 } // ndb_pushed_builder_ctx::is_pushable_as_child
824 
825 
826 /*********************
827  * This method examines a key item (could be part of a lookup key or a scan
828  * bound) for a table access operation and calculates the set of possible
829  * parents. (These are possible parent table access operations in the query
830  * tree that will be pushed to the ndb.)
831  *
832  * @param[in] table The table access operation to which the key item belongs.
833  * @param[in] key_item The key_item to examine
834  * @param[in] key_part Metatdata about the key item.
835  * @param[out] field_parents The set of possible parents for 'key_item'
836  * ('join_root' if keys are constant).
837  * @return True if at least one possible parent was found. (False means that
838  * operation cannot be pushed).
839  */
840 bool ndb_pushed_builder_ctx::is_field_item_pushable(
841  const AQP::Table_access* table,
842  const Item* key_item,
843  const KEY_PART_INFO* key_part,
844  ndb_table_access_map& field_parents)
845 {
846  DBUG_ENTER("is_field_item_pushable()");
847  const uint tab_no = table->get_access_no();
848  DBUG_ASSERT(key_item->type() == Item::FIELD_ITEM);
849 
850  const Item_field* const key_item_field
851  = static_cast<const Item_field*>(key_item);
852 
853  DBUG_PRINT("info", ("keyPart:%d, field:%s.%s",
854  (int)(key_item - table->get_key_field(0)),
855  key_item_field->field->table->alias,
856  key_item_field->field->field_name));
857 
858  if (!key_item_field->field->eq_def(key_part->field))
859  {
860  EXPLAIN_NO_PUSH("Can't push table '%s' as child, "
861  "column '%s' does not have same datatype as ref'ed "
862  "column '%s.%s'",
863  table->get_table()->alias,
864  key_part->field->field_name,
865  key_item_field->field->table->alias,
866  key_item_field->field->field_name);
867  m_tables[tab_no].m_maybe_pushable &= ~PUSHABLE_AS_CHILD; // Permanently disable as child
868  DBUG_RETURN(false);
869  }
870 
883 
884  // 0) Prepare for calculating parent candidates for this FIELD_ITEM
885  //
886  field_parents.clear_all();
887 
889  // 1) Add our existing parent reference to the set of parent candidates
890  //
891  uint referred_table_no= get_table_no(key_item_field);
892  if (m_join_scope.contain(referred_table_no))
893  {
894  field_parents.add(referred_table_no);
895  }
896 
898  // 2) Use the equality set to possibly find more parent candidates
899  // usable by substituting existing 'key_item_field'
900  //
901  Item_equal* item_equal= table->get_item_equal(key_item_field);
902  if (item_equal)
903  {
904  AQP::Equal_set_iterator equal_iter(*item_equal);
905  const Item_field* substitute_field;
906  while ((substitute_field= equal_iter.next()) != NULL)
907  {
908  if (substitute_field != key_item_field)
909  {
910  const uint substitute_table_no= get_table_no(substitute_field);
911  if (m_join_scope.contain(substitute_table_no))
912  {
913  DBUG_PRINT("info",
914  (" join_items[%d] %s.%s can be replaced with %s.%s",
915  (int)(key_item - table->get_key_field(0)),
916  get_referred_table_access_name(key_item_field),
917  get_referred_field_name(key_item_field),
918  get_referred_table_access_name(substitute_field),
919  get_referred_field_name(substitute_field)));
920 
921  field_parents.add(substitute_table_no);
922  }
923  }
924  } // while(substitute_field != NULL)
925  }
926  if (!field_parents.is_clear_all())
927  {
928  DBUG_RETURN(true);
929  }
930 
931  if (m_const_scope.contain(referred_table_no))
932  {
933  // This key item is const. and did not cause the set of possible parents
934  // to be recalculated. Reuse what we had before this key item.
935  DBUG_ASSERT(field_parents.is_clear_all());
940  if (!ndbcluster_is_lookup_operation(m_join_root->get_access_type()))
941  {
942  const TABLE* const referred_tab = key_item_field->field->table;
943  uint access_no = tab_no;
944  do
945  {
946  DBUG_ASSERT(access_no > 0);
947  access_no--;
948  if (m_plan.get_table_access(access_no)->uses_join_cache())
949  {
950  EXPLAIN_NO_PUSH("Cannot push table '%s' as child of '%s', since "
951  "it referes to column '%s.%s' which will be stored "
952  "in a join buffer.",
953  table->get_table()->alias,
954  m_join_root->get_table()->alias,
955  get_referred_table_access_name(key_item_field),
956  get_referred_field_name(key_item_field));
957  DBUG_RETURN(false);
958  }
959  } while (m_plan.get_table_access(access_no)->get_table()
960  != referred_tab);
961 
962  } // if (!ndbcluster_is_lookup_operation(root_type)
963  field_parents= ndb_table_access_map(m_join_root->get_access_no());
964  DBUG_RETURN(true);
965  }
966  else
967  {
968  EXPLAIN_NO_PUSH("Can't push table '%s' as child of '%s', "
969  "column '%s.%s' is outside scope of pushable join",
970  table->get_table()->alias, m_join_root->get_table()->alias,
971  get_referred_table_access_name(key_item_field),
972  get_referred_field_name(key_item_field));
973  DBUG_RETURN(false);
974  }
975 } // ndb_pushed_builder_ctx::is_field_item_pushable()
976 
977 
978 bool ndb_pushed_builder_ctx::is_const_item_pushable(
979  const Item* key_item,
980  const KEY_PART_INFO* key_part)
981 {
982  DBUG_ENTER("is_const_item_pushable()");
983  DBUG_ASSERT(key_item->const_item());
984 
989  Field* const field= key_part->field;
990  const int error=
991  const_cast<Item*>(key_item)->save_in_field_no_warnings(field, true);
992  if (unlikely(error))
993  {
994  DBUG_PRINT("info", ("Failed to store constant Item into Field -> not"
995  " pushable"));
996  DBUG_RETURN(false);
997  }
998  if (field->is_real_null())
999  {
1000  DBUG_PRINT("info", ("NULL constValues in key -> not pushable"));
1001  DBUG_RETURN(false); // TODO, handle gracefull -> continue?
1002  }
1003  DBUG_RETURN(true);
1004 } // ndb_pushed_builder_ctx::is_const_item_pushable()
1005 
1006 
1007 int
1008 ndb_pushed_builder_ctx::optimize_query_plan()
1009 {
1010  DBUG_ENTER("optimize_query_plan");
1011  const uint root_no= m_join_root->get_access_no();
1012 
1013  // Find an optimal order for joining the tables
1014  for (uint tab_no= m_plan.get_access_count()-1;
1015  tab_no > root_no;
1016  tab_no--)
1017  {
1018  struct pushed_tables &table= m_tables[tab_no];
1019  if (!m_join_scope.contain(tab_no))
1020  continue;
1021 
1028  if (!table.m_depend_parents.is_clear_all())
1029  {
1030  ndb_table_access_map const &dependency= table.m_depend_parents;
1031  DBUG_ASSERT(!dependency.contain(tab_no)); // Circular dependency!
1032 
1033  uint depends_on_parent= dependency.last_table(tab_no-1);
1034  ndb_table_access_map dependency_mask;
1035  dependency_mask.set_prefix(depends_on_parent);
1036 
1037  if (table.m_extend_parents.is_overlapping(dependency_mask))
1038  {
1039  table.m_extend_parents.subtract(dependency_mask);
1040  DBUG_ASSERT(table.m_extend_parents.contain(depends_on_parent) ||
1041  m_plan.get_table_access(depends_on_parent)->get_join_type(m_join_root) == AQP::JT_INNER_JOIN);
1042  table.m_extend_parents.add(depends_on_parent);
1043  }
1044  if (table.m_common_parents.is_overlapping(dependency_mask))
1045  {
1046  table.m_common_parents.clear_all();
1047  }
1048  }
1049 
1054  uint parent_no;
1055  ndb_table_access_map const &parents=
1056  table.m_common_parents.is_clear_all()
1057  ? table.m_extend_parents
1058  : table.m_common_parents;
1059 
1060  DBUG_ASSERT(!parents.is_clear_all());
1061  DBUG_ASSERT(!parents.contain(tab_no)); // No circular dependency!
1062 
1068  parent_no= parents.first_table(root_no);
1069  DBUG_ASSERT(parent_no < tab_no);
1070  table.m_parent= parent_no;
1071 
1072  ndb_table_access_map dependency(table.m_depend_parents);
1073  dependency.clear_bit(parent_no);
1074  m_tables[parent_no].m_depend_parents.add(dependency);
1075  }
1076 
1077  /* Build the set of ancestors available through the selected 'm_parent' */
1078  for (uint tab_no= root_no+1;
1079  tab_no < m_plan.get_access_count();
1080  tab_no++)
1081  {
1082  if (m_join_scope.contain(tab_no))
1083  {
1084  struct pushed_tables &table= m_tables[tab_no];
1085  const uint parent_no= table.m_parent;
1086  table.m_ancestors= m_tables[parent_no].m_ancestors;
1087  table.m_ancestors.add(parent_no);
1088  DBUG_ASSERT(table.m_ancestors.contain(table.m_depend_parents));
1089  }
1090  }
1091  DBUG_RETURN(0);
1092 } // ndb_pushed_builder_ctx::optimize_query_plan
1093 
1094 
1095 void
1096 ndb_pushed_builder_ctx::collect_key_refs(
1097  const AQP::Table_access* table,
1098  const Item* key_refs[]) const
1099 {
1100  DBUG_ENTER("collect_key_refs");
1101 
1102  const uint tab_no= table->get_access_no();
1103  const uint parent_no= m_tables[tab_no].m_parent;
1104  const ndb_table_access_map& ancestors= m_tables[tab_no].m_ancestors;
1105 
1106  DBUG_ASSERT(m_join_scope.contain(ancestors));
1107  DBUG_ASSERT(ancestors.contain(parent_no));
1108 
1114  for (uint key_part_no= 0;
1115  key_part_no < table->get_no_of_key_fields();
1116  key_part_no++)
1117  {
1118  const Item* const key_item= table->get_key_field(key_part_no);
1119  key_refs[key_part_no]= key_item;
1120 
1121  DBUG_ASSERT(key_item->const_item() || key_item->type()==Item::FIELD_ITEM);
1122 
1123  if (key_item->type() == Item::FIELD_ITEM)
1124  {
1125  const Item_field* join_item= static_cast<const Item_field*>(key_item);
1126  uint referred_table_no= get_table_no(join_item);
1127  Item_equal* item_equal;
1128 
1129  if (referred_table_no != parent_no &&
1130  (item_equal= table->get_item_equal(join_item)) != NULL)
1131  {
1132  AQP::Equal_set_iterator iter(*item_equal);
1133  const Item_field* substitute_field;
1134  while ((substitute_field= iter.next()) != NULL)
1135  {
1137  // Prefer to replace join_item with ref. to selected parent.
1138  //
1139  const uint substitute_table_no= get_table_no(substitute_field);
1140  if (substitute_table_no == parent_no)
1141  {
1142  DBUG_PRINT("info",
1143  (" Replacing key_refs[%d] %s.%s with %s.%s (parent)",
1144  key_part_no,
1145  get_referred_table_access_name(join_item),
1146  get_referred_field_name(join_item),
1147  get_referred_table_access_name(substitute_field),
1148  get_referred_field_name(substitute_field)));
1149 
1150  referred_table_no= substitute_table_no;
1151  key_refs[key_part_no]= join_item= substitute_field;
1152  break;
1153  }
1154  else if (ancestors.contain(substitute_table_no))
1155  {
1156  DBUG_ASSERT(substitute_table_no <= parent_no);
1157 
1159  // Second best is to replace join_item with closest grandparent ref.
1160  // In this case we will continue to search for the common parent match:
1161  // Updates key_refs[] if:
1162  // 1): Replace incorrect refs of tables not being an 'ancestor'.
1163  // 2): Found a better substitute closer to selected parent
1164  //
1165  if (!ancestors.contain(referred_table_no) || // 1
1166  referred_table_no < substitute_table_no) // 2)
1167  {
1168  DBUG_PRINT("info",
1169  (" Replacing key_refs[%d] %s.%s with %s.%s (grandparent)",
1170  key_part_no,
1171  get_referred_table_access_name(join_item),
1172  get_referred_field_name(join_item),
1173  get_referred_table_access_name(substitute_field),
1174  get_referred_field_name(substitute_field)));
1175 
1176  referred_table_no= substitute_table_no;
1177  key_refs[key_part_no]= join_item= substitute_field;
1178  }
1179  }
1180  } // while (substitute...
1181 
1182  DBUG_ASSERT (referred_table_no == parent_no ||
1183  !m_join_scope.contain(referred_table_no) || // Is a 'const' paramValue
1184  !m_tables[tab_no].m_common_parents.contain(parent_no));
1185  }
1186  } // Item::FIELD_ITEM
1187  }
1188 
1189  key_refs[table->get_no_of_key_fields()]= NULL;
1190  DBUG_VOID_RETURN;
1191 } // ndb_pushed_builder_ctx::collect_key_refs()
1192 
1193 
1194 int
1195 ndb_pushed_builder_ctx::build_key(const AQP::Table_access* table,
1196  const NdbQueryOperand *op_key[])
1197 {
1198  DBUG_ENTER("build_key");
1199  DBUG_ASSERT(m_join_scope.contain(table->get_access_no()));
1200 
1201  const KEY* const key= &table->get_table()->key_info[table->get_index_no()];
1202  op_key[0]= NULL;
1203 
1204  if (table == m_join_root)
1205  {
1206  if (ndbcluster_is_lookup_operation(table->get_access_type()))
1207  {
1208  for (uint i= 0; i < key->user_defined_key_parts; i++)
1209  {
1210  op_key[i]= m_builder->paramValue();
1211  if (unlikely(op_key[i] == NULL))
1212  {
1213  DBUG_RETURN(-1);
1214  }
1215  }
1216  op_key[key->user_defined_key_parts]= NULL;
1217  }
1218  }
1219  else
1220  {
1221  const uint key_fields= table->get_no_of_key_fields();
1222  DBUG_ASSERT(key_fields > 0 && key_fields <= key->user_defined_key_parts);
1224 
1225  if (ndbcluster_is_lookup_operation(table->get_access_type()))
1226  {
1227  const ha_ndbcluster* handler=
1228  static_cast<ha_ndbcluster*>(table->get_table()->file);
1229  ndbcluster_build_key_map(handler->m_table,
1230  handler->m_index[table->get_index_no()],
1231  key, map);
1232  }
1233  else
1234  {
1235  for (uint ix = 0; ix < key_fields; ix++)
1236  {
1237  map[ix]= ix;
1238  }
1239  }
1240 
1241  const Item* join_items[ndb_pushed_join::MAX_LINKED_KEYS+1];
1242  collect_key_refs(table,join_items);
1243 
1244  const KEY_PART_INFO *key_part= key->key_part;
1245  for (uint i= 0; i < key_fields; i++, key_part++)
1246  {
1247  const Item* const item= join_items[i];
1248  op_key[map[i]]= NULL;
1249 
1250  DBUG_ASSERT(item->const_item() == item->const_during_execution());
1251  if (item->const_item())
1252  {
1257  Field* const field= key_part->field;
1258  DBUG_ASSERT(!field->is_real_null());
1259  const uchar* const ptr= (field->real_type() == MYSQL_TYPE_VARCHAR)
1260  ? field->ptr + ((Field_varstring*)field)->length_bytes
1261  : field->ptr;
1262 
1263  op_key[map[i]]= m_builder->constValue(ptr, field->data_length());
1264  }
1265  else
1266  {
1267  DBUG_ASSERT(item->type() == Item::FIELD_ITEM);
1268  const Item_field* const field_item= static_cast<const Item_field*>(item);
1269  const uint referred_table_no= get_table_no(field_item);
1270 
1271  if (m_join_scope.contain(referred_table_no))
1272  {
1273  // Locate the parent operation for this 'join_items[]'.
1274  // May refer any of the preceeding parent tables
1275  const NdbQueryOperationDef* const parent_op= m_tables[referred_table_no].m_op;
1276  DBUG_ASSERT(parent_op != NULL);
1277 
1278  // TODO use field_index ??
1279  op_key[map[i]]= m_builder->linkedValue(parent_op,
1280  field_item->field_name);
1281  }
1282  else
1283  {
1284  DBUG_ASSERT(m_const_scope.contain(referred_table_no));
1285  // Outside scope of join plan, Handle as parameter as its value
1286  // will be known when we are ready to execute this query.
1287  if (unlikely(m_fld_refs >= ndb_pushed_join::MAX_REFERRED_FIELDS))
1288  {
1289  DBUG_PRINT("info", ("Too many Field refs ( >= MAX_REFERRED_FIELDS) "
1290  "encountered"));
1291  DBUG_RETURN(-1); // TODO, handle gracefull -> continue?
1292  }
1293  m_referred_fields[m_fld_refs++]= field_item->field;
1294  op_key[map[i]]= m_builder->paramValue();
1295  }
1296  }
1297 
1298  if (unlikely(op_key[map[i]] == NULL))
1299  {
1300  DBUG_RETURN(-1);
1301  }
1302  }
1303  op_key[key_fields]= NULL;
1304  }
1305  DBUG_RETURN(0);
1306 } // ndb_pushed_builder_ctx::build_key()
1307 
1308 
1309 int
1310 ndb_pushed_builder_ctx::build_query()
1311 {
1312  DBUG_ENTER("build_query");
1313 
1314  DBUG_PRINT("enter", ("Table %d as root is pushable", m_join_root->get_access_no()));
1315  DBUG_EXECUTE("info", m_join_root->dbug_print(););
1316 
1317  uint root_no= m_join_root->get_access_no();
1318  DBUG_ASSERT(m_join_scope.contain(root_no));
1319 
1320  if (m_builder == NULL)
1321  {
1322  m_builder= NdbQueryBuilder::create();
1323  if (unlikely (m_builder==NULL))
1324  {
1325  DBUG_RETURN(HA_ERR_OUT_OF_MEM);
1326  }
1327  }
1328 
1329  for (uint tab_no= root_no; tab_no<m_plan.get_access_count(); tab_no++)
1330  {
1331  if (!m_join_scope.contain(tab_no))
1332  continue;
1333 
1334  const AQP::Table_access* const table= m_plan.get_table_access(tab_no);
1335  const AQP::enum_access_type access_type= table->get_access_type();
1336  const ha_ndbcluster* handler=
1337  static_cast<ha_ndbcluster*>(table->get_table()->file);
1338 
1340  if (table->get_index_no() >= 0)
1341  {
1342  const int error= build_key(table, op_key);
1343  if (unlikely(error))
1344  DBUG_RETURN(error);
1345  }
1346 
1347  NdbQueryOptions options;
1348  if (handler->m_cond)
1349  {
1350  NdbInterpretedCode code(handler->m_table);
1351  if (handler->m_cond->generate_scan_filter(&code, NULL) != 0)
1352  {
1353 // ERR_RETURN(code.getNdbError()); // FIXME
1354  }
1355  options.setInterpretedCode(code);
1356  }
1357  if (table != m_join_root)
1358  {
1359  DBUG_ASSERT(m_tables[tab_no].m_parent!=MAX_TABLES);
1360  const uint parent_no= m_tables[tab_no].m_parent;
1361  const AQP::Table_access* parent= m_plan.get_table_access(parent_no);
1362 
1363  if (!m_tables[tab_no].m_common_parents.contain(parent_no))
1364  {
1365  DBUG_ASSERT(m_tables[parent_no].m_op != NULL);
1366  options.setParent(m_tables[parent_no].m_op);
1367  }
1368  if (table->get_join_type(parent) == AQP::JT_INNER_JOIN)
1369  {
1370  options.setMatchType(NdbQueryOptions::MatchNonNull);
1371  }
1372  }
1373 
1374  const NdbQueryOperationDef* query_op= NULL;
1375  if (ndbcluster_is_lookup_operation(access_type))
1376  {
1377  // Primary key access assumed
1378  if (access_type == AQP::AT_PRIMARY_KEY ||
1379  access_type == AQP::AT_MULTI_PRIMARY_KEY)
1380  {
1381  DBUG_PRINT("info", ("Operation is 'primary-key-lookup'"));
1382  query_op= m_builder->readTuple(handler->m_table, op_key, &options);
1383  }
1384  else
1385  {
1386  DBUG_ASSERT(access_type == AQP::AT_UNIQUE_KEY);
1387  DBUG_PRINT("info", ("Operation is 'unique-index-lookup'"));
1388  const NdbDictionary::Index* const index
1389  = handler->m_index[table->get_index_no()].unique_index;
1390  DBUG_ASSERT(index);
1391  query_op= m_builder->readTuple(index, handler->m_table, op_key, &options);
1392  }
1393  } // ndbcluster_is_lookup_operation()
1394 
1401  else if (access_type == AQP::AT_ORDERED_INDEX_SCAN ||
1402  access_type == AQP::AT_MULTI_MIXED)
1403  {
1404  DBUG_ASSERT(table->get_index_no() >= 0);
1405  DBUG_ASSERT(handler->m_index[table->get_index_no()].index != NULL);
1406 
1407  DBUG_PRINT("info", ("Operation is 'equal-range-lookup'"));
1408  DBUG_PRINT("info", ("Creating scanIndex on index id:%d, name:%s",
1409  table->get_index_no(),
1410  handler->m_index[table->get_index_no()]
1411  .index->getName()));
1412 
1413  const NdbQueryIndexBound bounds(op_key);
1414  query_op= m_builder->scanIndex(handler->m_index[table->get_index_no()].index,
1415  handler->m_table, &bounds, &options);
1416  }
1417  else if (access_type == AQP::AT_TABLE_SCAN)
1418  {
1419  DBUG_PRINT("info", ("Operation is 'table scan'"));
1420  query_op= m_builder->scanTable(handler->m_table, &options);
1421  }
1422  else
1423  {
1424  DBUG_ASSERT(false);
1425  }
1426 
1427  if (unlikely(!query_op))
1428  DBUG_RETURN(-1);
1429 
1430  m_tables[tab_no].m_op= query_op;
1431  } // for (join_cnt= m_join_root->get_access_no(); join_cnt<plan.get_access_count(); join_cnt++)
1432 
1433  DBUG_RETURN(0);
1434 } // ndb_pushed_builder_ctx::build_query()
1435 
1436 
1441 void
1442 ndbcluster_build_key_map(const NDBTAB* table, const NDB_INDEX_DATA& index,
1443  const KEY *key_def,
1444  uint ix_map[])
1445 {
1446  uint ix;
1447 
1448  if (index.unique_index_attrid_map) // UNIQUE_ORDERED_INDEX or UNIQUE_INDEX
1449  {
1450  for (ix = 0; ix < key_def->user_defined_key_parts; ix++)
1451  {
1452  ix_map[ix]= index.unique_index_attrid_map[ix];
1453  }
1454  }
1455  else // Primary key does not have a 'unique_index_attrid_map'
1456  {
1457  KEY_PART_INFO *key_part;
1458  uint key_pos= 0;
1459  int columnnr= 0;
1460  assert (index.type == PRIMARY_KEY_ORDERED_INDEX || index.type == PRIMARY_KEY_INDEX);
1461 
1462  for (ix = 0, key_part= key_def->key_part; ix < key_def->user_defined_key_parts; ix++, key_part++)
1463  {
1464  // As NdbColumnImpl::m_keyInfoPos isn't available through
1465  // NDB API we have to calculate it ourself, else we could:
1466  // ix_map[ix]= table->getColumn(key_part->fieldnr-1)->m_impl.m_keyInfoPos;
1467 
1468  if (key_part->fieldnr < columnnr)
1469  {
1470  // PK columns are not in same order as the columns are defined in the table,
1471  // Restart PK search from first column:
1472  key_pos=0;
1473  columnnr= 0;
1474  }
1475 
1476  while (columnnr < key_part->fieldnr-1)
1477  {
1478  if (table->getColumn(columnnr++)->getPrimaryKey())
1479  key_pos++;
1480  }
1481 
1482  assert(table->getColumn(columnnr)->getPrimaryKey());
1483  ix_map[ix]= key_pos;
1484 
1485  columnnr++;
1486  key_pos++;
1487  }
1488  }
1489 } // ndbcluster_build_key_map
1490 
1491 #endif // WITH_NDBCLUSTER_STORAGE_ENGINE