mysql5/mysql-5.7.27/sql/ha_ndbcluster_push.cc

1712 lines
58 KiB
C++

/*
Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
@file
@brief
This file defines various classes and methods used for pushing queries
to the ndb data node (for execution by the SPJ block).
*/
#include "ha_ndbcluster_glue.h"
#include "ha_ndbcluster.h"
#include "ha_ndbcluster_push.h"
#include "ha_ndbcluster_binlog.h"
#include "ha_ndbcluster_cond.h"
#include "abstract_query_plan.h"
#include <ndbapi/NdbApi.hpp>
#include <ndbapi/NdbInterpretedCode.hpp>
#include "../storage/ndb/src/ndbapi/NdbQueryBuilder.hpp"
#include "../storage/ndb/src/ndbapi/NdbQueryOperation.hpp"
#include <ndb_version.h>
/*
Explain why an operation could not be pushed
@param[in] msgfmt printf style format string.
*/
#define EXPLAIN_NO_PUSH(msgfmt, ...) \
do \
{ \
if (unlikely(current_thd->lex->describe)) \
{ \
push_warning_printf(current_thd, \
Sql_condition::SL_NOTE, ER_YES, \
(msgfmt), __VA_ARGS__); \
} \
} \
while(0)
static inline const char* get_referred_field_name(const Item_field* field_item)
{
DBUG_ASSERT(field_item->type() == Item::FIELD_ITEM);
return field_item->field->field_name;
}
static const char* get_referred_table_access_name(const Item_field* field_item)
{
DBUG_ASSERT(field_item->type() == Item::FIELD_ITEM);
return field_item->field->table->alias;
}
static bool ndbcluster_is_lookup_operation(AQP::enum_access_type accessType)
{
return accessType == AQP::AT_PRIMARY_KEY ||
accessType == AQP::AT_MULTI_PRIMARY_KEY ||
accessType == AQP::AT_UNIQUE_KEY;
}
uint
ndb_table_access_map::first_table(uint start) const
{
for (uint table_no= start; table_no<length(); table_no++)
{
if (contain(table_no))
return table_no;
}
return length();
}
uint
ndb_table_access_map::last_table(uint start) const
{
uint table_no= start;
while(true)
{
if (contain(table_no))
return table_no;
else if (table_no == 0)
return length();
table_no--;
}
}
ndb_pushed_join::ndb_pushed_join(
const ndb_pushed_builder_ctx& builder,
const NdbQueryDef* query_def)
:
m_query_def(query_def),
m_operation_count(0),
m_field_count(builder.m_fld_refs)
{
DBUG_ASSERT(query_def != NULL);
DBUG_ASSERT(builder.m_fld_refs <= MAX_REFERRED_FIELDS);
ndb_table_access_map searched;
for (uint tab_no= 0; !(searched==builder.m_join_scope); tab_no++)
{
const AQP::Table_access* const join_tab= builder.m_plan.get_table_access(tab_no);
if (builder.m_join_scope.contain(tab_no))
{
DBUG_ASSERT(m_operation_count < MAX_PUSHED_OPERATIONS);
m_tables[m_operation_count++] = join_tab->get_table();
searched.add(tab_no);
}
}
for (uint i= 0; i < builder.m_fld_refs; i++)
{
m_referred_fields[i] = builder.m_referred_fields[i];
}
}
ndb_pushed_join::~ndb_pushed_join()
{
if (m_query_def)
m_query_def->destroy();
}
bool ndb_pushed_join::match_definition(
int type, //NdbQueryOperationDef::Type,
const NDB_INDEX_DATA* idx) const
{
const NdbQueryOperationDef* const root_operation=
m_query_def->getQueryOperation((uint)0);
const NdbQueryOperationDef::Type def_type=
root_operation->getType();
if (def_type != type)
{
DBUG_PRINT("info",
("Cannot execute push join. Root operation prepared as %s "
"not executable as %s",
NdbQueryOperationDef::getTypeName(def_type),
NdbQueryOperationDef::getTypeName((NdbQueryOperationDef::Type)type)));
return FALSE;
}
const NdbDictionary::Index* const expected_index= root_operation->getIndex();
// Check that we still use the same index as when the query was prepared.
switch (def_type)
{
case NdbQueryOperationDef::PrimaryKeyAccess:
DBUG_ASSERT(idx!=NULL);
DBUG_ASSERT(idx->unique_index == expected_index);
break;
case NdbQueryOperationDef::UniqueIndexAccess:
DBUG_ASSERT(idx!=NULL);
// DBUG_ASSERT(idx->unique_index == expected_index);
if (idx->unique_index != expected_index)
{
DBUG_PRINT("info", ("Actual index %s differs from expected index %s."
"Therefore, join cannot be pushed.",
idx->unique_index->getName(),
expected_index->getName()));
return FALSE;
}
break;
case NdbQueryOperationDef::TableScan:
DBUG_ASSERT (idx==NULL && expected_index==NULL);
break;
case NdbQueryOperationDef::OrderedIndexScan:
DBUG_ASSERT(idx!=NULL);
// DBUG_ASSERT(idx->index == expected_index);
if (idx->index != expected_index)
{
DBUG_PRINT("info", ("Actual index %s differs from expected index %s. "
"Therefore, join cannot be pushed.",
idx->index->getName(),
expected_index->getName()));
return FALSE;
}
break;
default:
DBUG_ASSERT(false);
break;
}
/**
* There may be referrences to Field values from tables outside the scope of
* our pushed join which are supplied as paramValues().
* If any of these are NULL values, join can't be pushed.
*/
for (uint i= 0; i < get_field_referrences_count(); i++)
{
Field* field= m_referred_fields[i];
if (field->is_real_null())
{
DBUG_PRINT("info",
("paramValue is NULL, can not execute as pushed join"));
return FALSE;
}
}
return TRUE;
}
NdbQuery* ndb_pushed_join::make_query_instance(
NdbTransaction* trans,
const NdbQueryParamValue* keyFieldParams,
uint paramCnt) const
{
DBUG_ENTER("make_query_instance");
DBUG_PRINT("info",
("executing chain of %d pushed joins."
" First table is %s, accessed as %s.",
get_operation_count(),
get_table(0)->alias,
NdbQueryOperationDef::getTypeName(
m_query_def->getQueryOperation((uint)0)->getType())
));
const NdbQueryParamValue* paramValues = keyFieldParams;
/**
* There may be referrences to Field values from tables outside the scope of
* our pushed join: These are expected to be supplied as paramValues()
* after the keyFieldParams[].
*/
uint outer_fields= get_field_referrences_count();
NdbQueryParamValue* extendedParams = NULL;
if (unlikely(outer_fields > 0))
{
uint size= sizeof(NdbQueryParamValue) * (paramCnt+outer_fields);
extendedParams = reinterpret_cast<NdbQueryParamValue*>(my_alloca(size));
// Copy specified keyFieldParams[] first
for (uint i= 0; i < paramCnt; i++)
{
new (extendedParams + i) NdbQueryParamValue(keyFieldParams[i]);
}
// There may be referrences to Field values from tables outside the scope of
// our pushed join: These are expected to be supplied as paramValues()
for (uint i= 0; i < outer_fields; i++)
{
Field* field= m_referred_fields[i];
DBUG_ASSERT(!field->is_real_null()); // Checked by ::check_if_pushable()
new (extendedParams + paramCnt + i) NdbQueryParamValue(field->ptr, false);
}
paramValues= extendedParams;
}
NdbQuery* query= trans->createQuery(&get_query_def(), paramValues);
if (unlikely(extendedParams != NULL))
{
for (uint i = 0; i < paramCnt + outer_fields; i++)
{
extendedParams[i].~NdbQueryParamValue();
}
}
DBUG_RETURN(query);
}
/////////////////////////////////////////
ndb_pushed_builder_ctx::ndb_pushed_builder_ctx(const AQP::Join_plan& plan)
:
m_plan(plan),
m_join_root(),
m_join_scope(),
m_const_scope(),
m_firstmatch_skipped(),
m_internal_op_count(0),
m_fld_refs(0),
m_builder(NULL)
{
const uint count= m_plan.get_access_count();
(void)ha_ndb_ext; // Prevents compiler warning.
DBUG_ASSERT(count <= MAX_TABLES);
if (count > 1)
{
for (uint i= 0; i < count; i++)
{
m_tables[i].m_maybe_pushable= 0;
const AQP::Table_access* const table = m_plan.get_table_access(i);
if (table->get_table()->s->db_type()->db_type != DB_TYPE_NDBCLUSTER)
{
DBUG_PRINT("info", ("Table '%s' not in ndb engine, not pushable",
table->get_table()->alias));
continue;
}
switch (table->get_access_type())
{
case AQP::AT_VOID:
DBUG_ASSERT(false);
break;
case AQP::AT_FIXED:
EXPLAIN_NO_PUSH("Table '%s' was optimized away, or const'ified by optimizer",
table->get_table()->alias);
break;
case AQP::AT_OTHER:
EXPLAIN_NO_PUSH("Table '%s' is not pushable: %s",
table->get_table()->alias,
table->get_other_access_reason());
break;
case AQP::AT_UNDECIDED:
EXPLAIN_NO_PUSH("Table '%s' is not pushable: "
"Access type was not chosen at 'prepare' time",
table->get_table()->alias);
break;
default:
const char* reason= NULL;
const ha_ndbcluster* handler=
static_cast<ha_ndbcluster*>(table->get_table()->file);
if (handler->maybe_pushable_join(reason))
{
m_tables[i].m_maybe_pushable= PUSHABLE_AS_CHILD | PUSHABLE_AS_PARENT;
}
else if (reason != NULL)
{
EXPLAIN_NO_PUSH("Table '%s' is not pushable: %s",
table->get_table()->alias, reason);
}
break;
} //switch
/**
* FirstMatch algorithm may skip further nested-loop evaluation
* if this, and possible a number of previous tables.
* Aggregate into the bitmap 'm_firstmatch_skipped' those tables
* which 'FirstMatch' usage may possible skip.
*/
const AQP::Table_access* const firstmatch_last_skipped=
table->get_firstmatch_last_skipped();
if (firstmatch_last_skipped)
{
const uint last_skipped_tab= firstmatch_last_skipped->get_access_no();
DBUG_ASSERT(last_skipped_tab <= i);
for (uint skip_tab= last_skipped_tab; skip_tab <= i; skip_tab++)
{
m_firstmatch_skipped.add(skip_tab);
}
}
} //for 'all tables'
m_tables[0].m_maybe_pushable &= ~PUSHABLE_AS_CHILD;
m_tables[count-1].m_maybe_pushable &= ~PUSHABLE_AS_PARENT;
// Fill in table for maping internal <-> external table enumeration
for (uint i= 0; i < count; i++)
{
const AQP::Table_access* const table = m_plan.get_table_access(i);
uint external= table->get_table()->pos_in_table_list->tableno();
DBUG_ASSERT(external <= MAX_TABLES);
m_remap[i].to_external= external;
m_remap[external].to_internal= i;
}
}
} // ndb_pushed_builder_ctx::ndb_pushed_builder_ctx()
ndb_pushed_builder_ctx::~ndb_pushed_builder_ctx()
{
if (m_builder)
{
m_builder->destroy();
}
}
const NdbError& ndb_pushed_builder_ctx::getNdbError() const
{
DBUG_ASSERT(m_builder);
return m_builder->getNdbError();
}
/**
* Get *internal* table_no of table referred by 'key_item'
*/
uint
ndb_pushed_builder_ctx::get_table_no(const Item* key_item) const
{
DBUG_ASSERT(key_item->type() == Item::FIELD_ITEM);
table_map bitmap= key_item->used_tables();
for (uint i= 0; i<MAX_TABLES && bitmap!=0; i++, bitmap>>=1)
{
if (bitmap & 1)
{
DBUG_ASSERT(bitmap == 0x01); // Only a single table in 'bitmap'
return m_remap[i].to_internal;
}
}
return MAX_TABLES;
}
/**
* Main entry point to build a pushed join having 'join_root'
* as it first operation.
*
* If the root operation is pushable, we append as many 'child'
* operations as possible to the pushed join.
*
* This currently is implemented as a 3 pass algorithm:
*
* 1) Analyze each child and add it to 'm_join_scope' as
* 'pushable' if it qualifies as such. Part of this phase
* is also calculations of possible parents for each table.
*
* 2) Determine the parent to be used among the set of possible
* parents. This is decided based on simple heuristic where
* the goal is to employ filters as soon as possible,
* reduce the fanout of intermediate results, and utilize
* the parallelism of the SPJ block whenever considdered optimal.
*
* 3) Build the pushed query.
*
*/
int
ndb_pushed_builder_ctx::make_pushed_join(
const AQP::Table_access* join_root,
const ndb_pushed_join* &pushed_join)
{
DBUG_ENTER("make_pushed_join");
pushed_join= NULL;
if (is_pushable_with_root(join_root))
{
int error;
error= optimize_query_plan();
if (unlikely(error))
DBUG_RETURN(error);
error= build_query();
if (unlikely(error))
DBUG_RETURN(error);
const NdbQueryDef* const query_def= m_builder->prepare();
if (unlikely(query_def == NULL))
DBUG_RETURN(-1); // Get error with ::getNdbError()
pushed_join= new ndb_pushed_join(*this, query_def);
if (unlikely (pushed_join == NULL))
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
DBUG_PRINT("info", ("Created pushed join with %d child operations",
pushed_join->get_operation_count()-1));
}
DBUG_RETURN(0);
} // ndb_pushed_builder_ctx::make_pushed_join()
/**
* Find the number SPJ operations needed to execute a given access type.
* (Unique index lookups are translated to two single table lookups internally.)
*/
uint internal_operation_count(AQP::enum_access_type accessType)
{
switch (accessType)
{
case AQP::AT_PRIMARY_KEY:
case AQP::AT_ORDERED_INDEX_SCAN:
case AQP::AT_MULTI_PRIMARY_KEY:
case AQP::AT_MULTI_MIXED:
case AQP::AT_TABLE_SCAN:
return 1;
// Unique key lookups is mapped to two primary key lookups internally.
case AQP::AT_UNIQUE_KEY:
case AQP::AT_MULTI_UNIQUE_KEY:
return 2;
default:
// Other access types are not pushable, so seeing them here is an error.
DBUG_ASSERT(false);
return 2;
}
}
/**
* If there is a pushable query starting with 'root'; add as many
* child operations as possible to this 'ndb_pushed_builder_ctx' starting
* with that join_root.
*/
bool
ndb_pushed_builder_ctx::is_pushable_with_root(const AQP::Table_access* root)
{
DBUG_ENTER("is_pushable_with_root");
const uint root_no= root->get_access_no();
if ((m_tables[root_no].m_maybe_pushable & PUSHABLE_AS_PARENT) != PUSHABLE_AS_PARENT)
{
DBUG_PRINT("info", ("Table %d already reported 'not pushable_as_parent'", root_no));
DBUG_RETURN(false);
}
const AQP::enum_access_type access_type= root->get_access_type();
DBUG_ASSERT(access_type != AQP::AT_VOID);
if (access_type == AQP::AT_MULTI_UNIQUE_KEY)
{
EXPLAIN_NO_PUSH("Table '%s' is not pushable, "
"access type 'MULTI_UNIQUE_KEY' not implemented",
root->get_table()->alias);
m_tables[root_no].m_maybe_pushable &= ~PUSHABLE_AS_PARENT;
DBUG_RETURN(false);
}
if (root->filesort_before_join())
{
EXPLAIN_NO_PUSH("Table '%s' is not pushable, "
"need filesort before joining child tables",
root->get_table()->alias);
DBUG_RETURN(false);
}
/**
* Past this point we know at least root to be pushable as parent
* operation. Search remaining tables appendable if '::is_pushable_as_child()'
*/
DBUG_PRINT("info", ("Table %d is pushable as root", root->get_access_no()));
DBUG_EXECUTE("info", root->dbug_print(););
m_fld_refs= 0;
m_join_root= root;
m_const_scope.set_prefix(root_no);
m_join_scope= ndb_table_access_map(root_no);
m_internal_op_count = internal_operation_count(access_type);
uint push_cnt= 0;
for (uint tab_no= root->get_access_no()+1; tab_no<m_plan.get_access_count(); tab_no++)
{
const AQP::Table_access* const table= m_plan.get_table_access(tab_no);
if (is_pushable_as_child(table))
{
push_cnt++;
}
}
DBUG_RETURN(push_cnt>0);
} // ndb_pushed_builder_ctx::is_pushable_with_root()
/***************************************************************
* is_pushable_as_child()
*
* Determines if the specified child ('table') can be appended to
* an existing chain of previously pushed join operations.
*
* To be considdered pushable the child operation should:
*
* 1) Have an REF to the previous parent operations.
* 2) Refer only a single parent, or a grandparent reachable through
* a single parent common to all key fields in the 'REF'
*
* In order to increase pushability we use the COND_EQUAL sets
* to resolve cases (2) above) where multiple parents are refered.
* If needed too make a child pushable, we replace parent
* references with another from the COND_EQUAL sets which make
* it pushable .
****************************************************************/
bool
ndb_pushed_builder_ctx::is_pushable_as_child(
const AQP::Table_access* table)
{
DBUG_ENTER("is_pushable_as_child");
const uint root_no= m_join_root->get_access_no();
const uint tab_no= table->get_access_no();
DBUG_ASSERT(tab_no > root_no);
if ((m_tables[tab_no].m_maybe_pushable & PUSHABLE_AS_CHILD) != PUSHABLE_AS_CHILD)
{
DBUG_PRINT("info", ("Table %s already known 'not is_pushable_as_child'", table->get_table()->alias));
DBUG_RETURN(false);
}
const AQP::enum_access_type root_type= m_join_root->get_access_type();
const AQP::enum_access_type access_type= table->get_access_type();
if (!(ndbcluster_is_lookup_operation(access_type) ||
access_type==AQP::AT_ORDERED_INDEX_SCAN))
{
EXPLAIN_NO_PUSH("Can't push table '%s' as child, 'type' must be a 'ref' access",
table->get_table()->alias);
m_tables[tab_no].m_maybe_pushable &= ~PUSHABLE_AS_CHILD;
DBUG_RETURN(false);
}
// Currently there is a limitation in not allowing LOOKUP - (index)SCAN operations
if (access_type==AQP::AT_ORDERED_INDEX_SCAN &&
ndbcluster_is_lookup_operation(root_type))
{
EXPLAIN_NO_PUSH("Push of table '%s' as scan-child "
"with lookup-root '%s' not implemented",
table->get_table()->alias,
m_join_root->get_table()->alias);
// 'table' may still be PUSHABLE_AS_CHILD with another parent
DBUG_RETURN(false);
}
if (table->get_no_of_key_fields() > ndb_pushed_join::MAX_LINKED_KEYS)
{
EXPLAIN_NO_PUSH("Can't push table '%s' as child, "
"too many ref'ed parent fields",
table->get_table()->alias);
m_tables[tab_no].m_maybe_pushable &= ~PUSHABLE_AS_CHILD; // Permanently dissable
DBUG_RETURN(false);
}
for (uint i = tab_no; i > root_no; i--)
{
if (m_plan.get_table_access(i)->uses_join_cache())
{
EXPLAIN_NO_PUSH("Cannot push table '%s' as child of table '%s'. Doing so "
"would prevent using join buffer for table '%s'.",
table->get_table()->alias,
m_join_root->get_table()->alias,
m_plan.get_table_access(i)->get_table()->alias);
DBUG_RETURN(false);
}
}
// Check that we do not exceed the max number of pushable operations.
const uint internal_ops_needed = internal_operation_count(access_type);
if (unlikely(m_internal_op_count + internal_ops_needed
> NDB_SPJ_MAX_TREE_NODES))
{
EXPLAIN_NO_PUSH("Cannot push table '%s' as child of '%s'. Max number"
" of pushable tables exceeded.",
table->get_table()->alias,
m_join_root->get_table()->alias);
DBUG_RETURN(false);
}
m_internal_op_count += internal_ops_needed;
DBUG_PRINT("info", ("Table:%d, Checking %d REF keys", tab_no,
table->get_no_of_key_fields()));
/*****
* Calculate the set of possible parents for table, where:
* - 'current' are those currently being referred by the
* FIELD_ITEMs as set up by the MySQL optimizer.
* - 'common' are those we may refer (possibly through the EQ-sets)
* such that all FIELD_ITEMs are from the same parent.
* - 'extended' are those parents refered from some of the
* FIELD_ITEMs, and having the rest of the referred FIELD_ITEM
* tables available as 'grandparent refs'
* (The SPJ block can handle field references to any ancestor
* operation, not just the (direct) parent).
*
* In addition there are firm dependencies between some parents
* such that all 'depend_parents' must be referred as an ancestors
* of the table. By default 'depend_parents' will at least contain
* the most 'grandparent' of the extended parents.
*
****/
ndb_table_access_map current_parents;
ndb_table_access_map common_parents(m_join_scope);
ndb_table_access_map extend_parents;
ndb_table_access_map depend_parents;
for (uint key_part_no= 0;
key_part_no < table->get_no_of_key_fields();
key_part_no++)
{
const Item* const key_item= table->get_key_field(key_part_no);
const KEY_PART_INFO* key_part= table->get_key_part_info(key_part_no);
if (key_item->const_item()) // REF is a litteral or field from const-table
{
DBUG_PRINT("info", (" Item type:%d is 'const_item'", key_item->type()));
if (!is_const_item_pushable(key_item, key_part))
{
DBUG_RETURN(false);
}
}
else if (key_item->type() == Item::FIELD_ITEM)
{
/**
* Calculate all parents FIELD_ITEM may refer - Including those
* available through usage of equality sets.
*/
ndb_table_access_map field_parents;
if (!is_field_item_pushable(table, key_item, key_part, field_parents))
{
DBUG_RETURN(false);
}
/**
* Calculate 'current_parents' as the set of tables
* currently being referred by some 'key_item'.
*/
DBUG_ASSERT(key_item == table->get_key_field(key_part_no));
DBUG_ASSERT(key_item->type() == Item::FIELD_ITEM);
current_parents.add(get_table_no(key_item));
/**
* Calculate 'common_parents' as the set of possible 'field_parents'
* available from all 'key_part'.
*/
common_parents.intersect(field_parents);
/**
* 'Extended' parents are refered from some 'FIELD_ITEM', and contain
* all parents directly referred, or available as 'depend_parents'.
* The later excludes those before the first (grand-)parent
* available from all 'field_parents' (first_grandparent).
* However, it also introduce a dependency of those
* tables to really be available as grand parents.
*/
extend_parents.add(field_parents);
const uint first= field_parents.first_table(root_no);
depend_parents.add(first);
}
else
{
EXPLAIN_NO_PUSH("Can't push table '%s' as child, "
"column '%s' does neither 'ref' a column nor a constant",
table->get_table()->alias,
key_part->field->field_name);
m_tables[tab_no].m_maybe_pushable &= ~PUSHABLE_AS_CHILD; // Permanently disable as child
DBUG_RETURN(false);
}
} // for (uint key_part_no= 0 ...
if (m_const_scope.contain(current_parents))
{
// NOTE: This is a constant table wrt. this instance of the pushed join.
// It should be relatively simple to extend the SPJ block to
// allow such tables to be included in the pushed join.
EXPLAIN_NO_PUSH("Can't push table '%s' as child of '%s', "
"their dependency is 'const'",
table->get_table()->alias,
m_join_root->get_table()->alias);
DBUG_RETURN(false);
}
else if (extend_parents.is_clear_all())
{
EXPLAIN_NO_PUSH("Can't push table '%s' as child of '%s', "
"no parents found within scope",
table->get_table()->alias,
m_join_root->get_table()->alias);
DBUG_RETURN(false);
}
if (!ndbcluster_is_lookup_operation(table->get_access_type()))
{
/**
* Outer joining scan-scan is not supported, due to the following problem:
* Consider the query:
*
* select * from t1 left join t2
* on t1.attr=t2.ordered_index
* where predicate(t1.row, t2. row);
*
* Where 'predicate' cannot be pushed to the ndb. The ndb api may then
* return:
* +---------+---------+
* | t1.row1 | t2.row1 | (First batch)
* | t1.row2 | t2.row1 |
* ..... (NextReq).....
* | t1.row1 | t2.row2 | (Next batch)
* +---------+---------+
* Now assume that all rows but [t1.row1, t2.row1] satisfies 'predicate'.
* mysqld would be confused since the rows are not grouped on t1 values.
* It would therefor generate a NULL row such that it returns:
* +---------+---------+
* | t1.row1 | NULL | -> Error!
* | t1.row2 | t2.row1 |
* | t1.row1 | t2.row2 |
* +---------+---------+
*
* (Outer joining with scan may be indirect through lookup operations
* inbetween)
*/
if (table->get_join_type(m_join_root) == AQP::JT_OUTER_JOIN)
{
EXPLAIN_NO_PUSH("Can't push table '%s' as child of '%s', "
"outer join of scan-child not implemented",
table->get_table()->alias,
m_join_root->get_table()->alias);
DBUG_RETURN(false);
}
/**
* 'FirstMatch' is not allowed to skip over a scan-child.
* The reason is similar to the outer joined scan-scan above:
*
* Scan-scan result may return the same ancestor-scan rowset
* multiple times when rowset from child scan has to be fetched
* in multiple batches (as above).
*
* When a 'FirstMatch' skip remaining rows in a scan-child,
* the Nested Loop (NL) will also advance to the next ancestor
* row. However, due to child scan requiring multiple batches
* the same ancestor row will reappear in the next batch!
*/
if (m_firstmatch_skipped.contain(tab_no))
{
EXPLAIN_NO_PUSH("Can't push table '%s' as child of '%s', "
"'FirstMatch' not allowed to contain scan-child",
table->get_table()->alias,
m_join_root->get_table()->alias);
m_tables[tab_no].m_maybe_pushable &= ~PUSHABLE_AS_CHILD; // Permanently dissable
DBUG_RETURN(false);
}
/**
* Note, for both 'outer join' and 'FirstMatch' restriction above:
* The restriction could have been lifted if we could
* somehow ensure that all rows from a child scan are fetched
* before we move to the next ancestor row.
*/
} // scan operation
/**
* Check outer join restrictions if multiple 'depend_parents':
*
* If this table has multiple dependencies, it can only be added to
* the set of pushed tables if the dependent tables themself
* depends, or could be make dependent, on each other.
*
* Such new dependencies can only be added iff all 'depend_parents'
* are in the same 'inner join nest', i.e. we can not add *new*
* dependencies on outer joined tables.
* Any existing explicit specified outer joins are allowed though.
* Another way to view this is that the explained query plan
* should have no outer joins inbetween the table and the tables
* it joins with.
*
* Algorithm:
* 1. Calculate a single 'common ancestor' for all dependent tables
* which is the closest ancestor which they all depends on.
* (directly or indirectly through other dependencies.)
*
* 2. For all ancestors in the 'depend_parents' set:
* If none of the children of this ancestor are already 'joined'
* with this ancestor, (eiter directly or indirectly) it need
* to be in an inner join relation with our common ancestor.
*/
DBUG_ASSERT(!depend_parents.is_clear_all());
DBUG_ASSERT(!depend_parents.contain(tab_no)); // Circular dependency!
ndb_table_access_map dependencies(depend_parents);
/**
* Calculate the single 'common ancestor' of all 'depend_parents'.
* Iterating all directly, and indirectly, 'depend_parents'
* until a single dependent ancestor remains.
*/
uint common_ancestor_no= tab_no;
while (true)
{
common_ancestor_no= dependencies.last_table(common_ancestor_no-1);
dependencies.clear_bit(common_ancestor_no);
if (dependencies.is_clear_all())
break;
const ndb_table_access_map &ancestor_dependencies=
m_tables[common_ancestor_no].m_depend_parents;
const uint first_ancestor=
ancestor_dependencies.last_table(common_ancestor_no-1);
dependencies.add(first_ancestor);
} //while
const AQP::Table_access* const common_ancestor=
m_plan.get_table_access(common_ancestor_no);
/**
* Check that no dependencies on outer joined 'common ancestor'
* need to be added in order to allow this new child to be joined.
*/
ndb_table_access_map child_dependencies;
dependencies= depend_parents;
for (uint ancestor_no= dependencies.last_table(tab_no-1);
ancestor_no!= common_ancestor_no;
ancestor_no= dependencies.last_table(ancestor_no-1))
{
const AQP::Table_access* const ancestor=
m_plan.get_table_access(ancestor_no);
/**
* If there are children of this ancestor which depends on it,
* (i.e 'joins with it') then this ancestor can only be added to our
* 'join nest' if it is inner joined with our common_ancestor.
*/
if (depend_parents.contain(ancestor_no) &&
ancestor->get_join_type(common_ancestor) == AQP::JT_OUTER_JOIN)
{
/**
* Found an outer joined ancestor which none of my parents
* can depend / join with:
*/
if (child_dependencies.is_clear_all())
{
/**
* As this was the last (outer joined) 'depend_parents',
* with no other mandatory dependencies, this table may still
* be added to the pushed join.
* However, it may contain 'common' & 'extend' parent candidates
* which may now be joined with this outer joined ancestor.
* These are removed below.
*/
DBUG_ASSERT(extend_parents.contain(common_parents));
for (uint parent_no= extend_parents.last_table(tab_no-1);
parent_no > ancestor_no;
parent_no= extend_parents.last_table(parent_no-1))
{
if (!m_tables[parent_no].m_depend_parents.contain(ancestor_no))
{
common_parents.clear_bit(parent_no);
extend_parents.clear_bit(parent_no);
}
}
DBUG_ASSERT(!extend_parents.is_clear_all());
}
else if (!child_dependencies.contain(ancestor_no))
{
/**
* No child of this ancestor depends (joins) with this ancestor,
* and adding it as a 'depend_parent' would introduce new
* dependencies on outer joined grandparents.
* We will not be allowed to add this table to the pushed join.
*/
EXPLAIN_NO_PUSH("Can't push table '%s' as child of '%s', "
"as it would introduce a dependency on "
"outer joined grandparent '%s'",
table->get_table()->alias,
m_join_root->get_table()->alias,
ancestor->get_table()->alias);
DBUG_RETURN(false);
}
}
// Aggregate dependency sets
child_dependencies.add(m_tables[ancestor_no].m_depend_parents);
dependencies.add(m_tables[ancestor_no].m_depend_parents);
} //for
DBUG_ASSERT(m_join_scope.contain(common_parents));
DBUG_ASSERT(m_join_scope.contain(extend_parents));
DBUG_ASSERT(extend_parents.is_clear_all() ||
extend_parents.contain(common_parents));
/**
* Register calculated parent sets - ::optimize() choose from these.
*/
m_tables[tab_no].m_common_parents= common_parents;
m_tables[tab_no].m_extend_parents= extend_parents;
m_tables[tab_no].m_depend_parents= depend_parents;
m_tables[tab_no].m_parent= MAX_TABLES;
m_tables[tab_no].m_maybe_pushable= 0; // Exclude from further pushing
m_join_scope.add(tab_no);
DBUG_RETURN(true);
} // ndb_pushed_builder_ctx::is_pushable_as_child
/*********************
* This method examines a key item (could be part of a lookup key or a scan
* bound) for a table access operation and calculates the set of possible
* parents. (These are possible parent table access operations in the query
* tree that will be pushed to the ndb.)
*
* @param[in] table The table access operation to which the key item belongs.
* @param[in] key_item The key_item to examine
* @param[in] key_part Metatdata about the key item.
* @param[out] field_parents The set of possible parents for 'key_item'
* ('join_root' if keys are constant).
* @return True if at least one possible parent was found. (False means that
* operation cannot be pushed).
*/
bool ndb_pushed_builder_ctx::is_field_item_pushable(
const AQP::Table_access* table,
const Item* key_item,
const KEY_PART_INFO* key_part,
ndb_table_access_map& field_parents)
{
DBUG_ENTER("is_field_item_pushable()");
const uint tab_no = table->get_access_no();
DBUG_ASSERT(key_item->type() == Item::FIELD_ITEM);
const Item_field* const key_item_field
= static_cast<const Item_field*>(key_item);
DBUG_PRINT("info", ("keyPart:%d, field:%s.%s",
(int)(key_item - table->get_key_field(0)),
key_item_field->field->table->alias,
key_item_field->field->field_name));
if (!key_item_field->field->eq_def(key_part->field))
{
EXPLAIN_NO_PUSH("Can't push table '%s' as child, "
"column '%s' does not have same datatype as ref'ed "
"column '%s.%s'",
table->get_table()->alias,
key_part->field->field_name,
key_item_field->field->table->alias,
key_item_field->field->field_name);
m_tables[tab_no].m_maybe_pushable &= ~PUSHABLE_AS_CHILD; // Permanently disable as child
DBUG_RETURN(false);
}
/**
* Below this point 'key_item_field' is a candidate for refering a parent table
* in a pushed join. It should either directly refer a parent common to all
* FIELD_ITEMs, or refer a grandparent of this common parent.
* There are different cases which should be handled:
*
* 1) 'key_item_field' may already refer one of the parent available within our
* pushed scope.
* 2) By using the equality set, we may find alternative parent references which
* may make this a pushed join.
*/
///////////////////////////////////////////////////////////////////
// 0) Prepare for calculating parent candidates for this FIELD_ITEM
//
field_parents.clear_all();
////////////////////////////////////////////////////////////////////
// 1) Add our existing parent reference to the set of parent candidates
//
uint referred_table_no= get_table_no(key_item_field);
if (m_join_scope.contain(referred_table_no))
{
field_parents.add(referred_table_no);
}
//////////////////////////////////////////////////////////////////
// 2) Use the equality set to possibly find more parent candidates
// usable by substituting existing 'key_item_field'
//
Item_equal* item_equal= table->get_item_equal(key_item_field);
if (item_equal)
{
AQP::Equal_set_iterator equal_iter(*item_equal);
const Item_field* substitute_field;
while ((substitute_field= equal_iter.next()) != NULL)
{
if (substitute_field != key_item_field)
{
const uint substitute_table_no= get_table_no(substitute_field);
if (m_join_scope.contain(substitute_table_no))
{
DBUG_PRINT("info",
(" join_items[%d] %s.%s can be replaced with %s.%s",
(int)(key_item - table->get_key_field(0)),
get_referred_table_access_name(key_item_field),
get_referred_field_name(key_item_field),
get_referred_table_access_name(substitute_field),
get_referred_field_name(substitute_field)));
field_parents.add(substitute_table_no);
}
}
} // while(substitute_field != NULL)
}
if (!field_parents.is_clear_all())
{
DBUG_RETURN(true);
}
if (m_const_scope.contain(referred_table_no))
{
// This key item is const. and did not cause the set of possible parents
// to be recalculated. Reuse what we had before this key item.
DBUG_ASSERT(field_parents.is_clear_all());
/**
* Field referrence is a 'paramValue' to a column value evaluated
* prior to the root of this pushed join candidate. Some restrictions
* applies to when a field reference is allowed in a pushed join:
*/
if (ndbcluster_is_lookup_operation(m_join_root->get_access_type()))
{
/**
* The 'eq_ref' access function join_read_key(), may optimize away
* key reads if the key for a requested row is the same as the
* previous. Thus, iff this is the root of a pushed lookup join
* we do not want it to contain childs with references to columns
* 'outside' the the pushed joins, as these may still change
* between calls to join_read_key() independent of the root key
* itself being the same.
*/
EXPLAIN_NO_PUSH("Cannot push table '%s' as child of '%s', since "
"it referes to column '%s.%s' prior to a "
"potential 'const' root.",
table->get_table()->alias,
m_join_root->get_table()->alias,
get_referred_table_access_name(key_item_field),
get_referred_field_name(key_item_field));
DBUG_RETURN(false);
}
else
{
/**
* Scan queries cannot be pushed if the pushed query may refer column
* values (paramValues) from rows stored in a join cache.
*/
const TABLE* const referred_tab = key_item_field->field->table;
uint access_no = tab_no;
do
{
if (m_plan.get_table_access(access_no)->uses_join_cache())
{
EXPLAIN_NO_PUSH("Cannot push table '%s' as child of '%s', since "
"it referes to column '%s.%s' which will be stored "
"in a join buffer.",
table->get_table()->alias,
m_join_root->get_table()->alias,
get_referred_table_access_name(key_item_field),
get_referred_field_name(key_item_field));
DBUG_RETURN(false);
}
DBUG_ASSERT(access_no > 0);
access_no--;
} while (m_plan.get_table_access(access_no)->get_table()
!= referred_tab);
} // if (!ndbcluster_is_lookup_operation(root_type)
field_parents= ndb_table_access_map(m_join_root->get_access_no());
DBUG_RETURN(true);
}
else
{
EXPLAIN_NO_PUSH("Can't push table '%s' as child of '%s', "
"column '%s.%s' is outside scope of pushable join",
table->get_table()->alias, m_join_root->get_table()->alias,
get_referred_table_access_name(key_item_field),
get_referred_field_name(key_item_field));
DBUG_RETURN(false);
}
} // ndb_pushed_builder_ctx::is_field_item_pushable()
bool ndb_pushed_builder_ctx::is_const_item_pushable(
const Item* key_item,
const KEY_PART_INFO* key_part)
{
DBUG_ENTER("is_const_item_pushable()");
DBUG_ASSERT(key_item->const_item());
/**
* Propagate Items constant value to Field containing the value of this
* key_part:
*/
Field* const field= key_part->field;
const int error=
const_cast<Item*>(key_item)->save_in_field_no_warnings(field, true);
if (unlikely(error))
{
DBUG_PRINT("info", ("Failed to store constant Item into Field -> not"
" pushable"));
DBUG_RETURN(false);
}
if (field->is_real_null())
{
DBUG_PRINT("info", ("NULL constValues in key -> not pushable"));
DBUG_RETURN(false); // TODO, handle gracefull -> continue?
}
DBUG_RETURN(true);
} // ndb_pushed_builder_ctx::is_const_item_pushable()
int
ndb_pushed_builder_ctx::optimize_query_plan()
{
DBUG_ENTER("optimize_query_plan");
const uint root_no= m_join_root->get_access_no();
for (uint tab_no= root_no; tab_no<m_plan.get_access_count(); tab_no++)
{
if (m_join_scope.contain(tab_no))
{
m_tables[tab_no].m_fanout = m_plan.get_table_access(tab_no)->get_fanout();
m_tables[tab_no].m_child_fanout = 1.0;
}
}
// Find an optimal order for joining the tables
for (uint tab_no= m_plan.get_access_count()-1;
tab_no > root_no;
tab_no--)
{
if (!m_join_scope.contain(tab_no))
continue;
/**
* Enforce the parent dependencies on the available
* 'common' and 'extended' parents set such that we
* don't skip any dependent parents from our ancestors
* when selecting the actuall 'm_parent' to be used.
*/
pushed_tables &table= m_tables[tab_no];
if (!table.m_depend_parents.is_clear_all())
{
ndb_table_access_map const &dependency= table.m_depend_parents;
DBUG_ASSERT(!dependency.contain(tab_no)); // Circular dependency!
uint depends_on_parent= dependency.last_table(tab_no-1);
ndb_table_access_map dependency_mask;
dependency_mask.set_prefix(depends_on_parent);
/**
* Remove any parent candidates prior to last 'depends_on_parent':
* All 'depend_parents' must be made available as grandparents
* prior to joining with any 'extend_parents' / 'common_parents'
*/
table.m_common_parents.subtract(dependency_mask);
table.m_extend_parents.subtract(dependency_mask);
/**
* Need some parent hints if all where cleared.
* Can always use closest depend_parent.
*/
if (table.m_extend_parents.is_clear_all())
{
table.m_extend_parents.add(depends_on_parent);
}
}
/**
* Select set to choose parent from, prefer a 'common'
* parent if available.
*/
uint parent_no;
ndb_table_access_map const &parents=
table.m_common_parents.is_clear_all()
? table.m_extend_parents
: table.m_common_parents;
DBUG_ASSERT(!parents.is_clear_all());
DBUG_ASSERT(!parents.contain(tab_no)); // No circular dependency!
/**
* In order to take advantage of the parallelism in the SPJ block;
* Initial parent candidate is the first possible among 'parents'.
* Will result in the most 'bushy' query plan (aka: star-join)
*/
parent_no= parents.first_table(root_no);
/**
* Push optimization for execution of child operations:
*
* To take advantage of the selectivity of parent operations we
* execute any parent operations with fanout <= 1 before this
* child operation. By making them depending on parent
* operations with high selectivity, child will be eliminated when
* the parent returns no matching rows.
*
* -> Execute child operation after any such parents
*/
for (uint candidate= parent_no+1; candidate<tab_no; candidate++)
{
if (parents.contain(candidate))
{
if (m_tables[candidate].m_fanout > 1.0)
break;
parent_no= candidate; // Parent candidate is selective, eval after
}
}
DBUG_ASSERT(parent_no < tab_no);
table.m_parent= parent_no;
m_tables[parent_no].m_child_fanout*= table.m_fanout*table.m_child_fanout;
/**
* Any remaining parent dependencies for this table has to be
* added to the selected parent in order to be taken into account
* for parent calculation for its ancestors.
*/
ndb_table_access_map dependency(table.m_depend_parents);
dependency.clear_bit(parent_no);
m_tables[parent_no].m_depend_parents.add(dependency);
}
/* Build the set of ancestors available through the selected 'm_parent' */
for (uint tab_no= root_no+1;
tab_no < m_plan.get_access_count();
tab_no++)
{
if (m_join_scope.contain(tab_no))
{
pushed_tables &table= m_tables[tab_no];
const uint parent_no= table.m_parent;
table.m_ancestors= m_tables[parent_no].m_ancestors;
table.m_ancestors.add(parent_no);
DBUG_ASSERT(table.m_ancestors.contain(table.m_depend_parents));
}
}
DBUG_RETURN(0);
} // ndb_pushed_builder_ctx::optimize_query_plan
void
ndb_pushed_builder_ctx::collect_key_refs(
const AQP::Table_access* table,
const Item* key_refs[]) const
{
DBUG_ENTER("collect_key_refs");
const uint tab_no= table->get_access_no();
const uint parent_no= m_tables[tab_no].m_parent;
const ndb_table_access_map& ancestors= m_tables[tab_no].m_ancestors;
DBUG_ASSERT(m_join_scope.contain(ancestors));
DBUG_ASSERT(ancestors.contain(parent_no));
/**
* If there are any key_fields with 'current_parents' different from
* our selected 'parent', we have to find substitutes for
* those key_fields within the equality set.
**/
for (uint key_part_no= 0;
key_part_no < table->get_no_of_key_fields();
key_part_no++)
{
const Item* const key_item= table->get_key_field(key_part_no);
key_refs[key_part_no]= key_item;
DBUG_ASSERT(key_item->const_item() || key_item->type()==Item::FIELD_ITEM);
if (key_item->type() == Item::FIELD_ITEM)
{
const Item_field* join_item= static_cast<const Item_field*>(key_item);
uint referred_table_no= get_table_no(join_item);
Item_equal* item_equal;
if (referred_table_no != parent_no &&
(item_equal= table->get_item_equal(join_item)) != NULL)
{
AQP::Equal_set_iterator iter(*item_equal);
const Item_field* substitute_field;
while ((substitute_field= iter.next()) != NULL)
{
///////////////////////////////////////////////////////////
// Prefer to replace join_item with ref. to selected parent.
//
const uint substitute_table_no= get_table_no(substitute_field);
if (substitute_table_no == parent_no)
{
DBUG_PRINT("info",
(" Replacing key_refs[%d] %s.%s with %s.%s (parent)",
key_part_no,
get_referred_table_access_name(join_item),
get_referred_field_name(join_item),
get_referred_table_access_name(substitute_field),
get_referred_field_name(substitute_field)));
referred_table_no= substitute_table_no;
key_refs[key_part_no]= join_item= substitute_field;
break;
}
else if (ancestors.contain(substitute_table_no))
{
DBUG_ASSERT(substitute_table_no <= parent_no);
//////////////////////////////////////////////////////////////////////
// Second best is to replace join_item with closest grandparent ref.
// In this case we will continue to search for the common parent match:
// Updates key_refs[] if:
// 1): Replace incorrect refs of tables not being an 'ancestor'.
// 2): Found a better substitute closer to selected parent
//
if (!ancestors.contain(referred_table_no) || // 1
referred_table_no < substitute_table_no) // 2)
{
DBUG_PRINT("info",
(" Replacing key_refs[%d] %s.%s with %s.%s (grandparent)",
key_part_no,
get_referred_table_access_name(join_item),
get_referred_field_name(join_item),
get_referred_table_access_name(substitute_field),
get_referred_field_name(substitute_field)));
referred_table_no= substitute_table_no;
key_refs[key_part_no]= join_item= substitute_field;
}
}
} // while (substitute...
DBUG_ASSERT (referred_table_no == parent_no ||
!m_join_scope.contain(referred_table_no) || // Is a 'const' paramValue
!m_tables[tab_no].m_common_parents.contain(parent_no));
}
} // Item::FIELD_ITEM
}
key_refs[table->get_no_of_key_fields()]= NULL;
DBUG_VOID_RETURN;
} // ndb_pushed_builder_ctx::collect_key_refs()
int
ndb_pushed_builder_ctx::build_key(const AQP::Table_access* table,
const NdbQueryOperand *op_key[])
{
DBUG_ENTER("build_key");
DBUG_ASSERT(m_join_scope.contain(table->get_access_no()));
const KEY* const key= &table->get_table()->key_info[table->get_index_no()];
op_key[0]= NULL;
if (table == m_join_root)
{
if (ndbcluster_is_lookup_operation(table->get_access_type()))
{
for (uint i= 0; i < key->user_defined_key_parts; i++)
{
op_key[i]= m_builder->paramValue();
if (unlikely(op_key[i] == NULL))
{
DBUG_RETURN(-1);
}
}
op_key[key->user_defined_key_parts]= NULL;
}
}
else
{
const uint key_fields= table->get_no_of_key_fields();
DBUG_ASSERT(key_fields > 0 && key_fields <= key->user_defined_key_parts);
uint map[ndb_pushed_join::MAX_LINKED_KEYS+1];
if (ndbcluster_is_lookup_operation(table->get_access_type()))
{
const ha_ndbcluster* handler=
static_cast<ha_ndbcluster*>(table->get_table()->file);
ndbcluster_build_key_map(handler->m_table,
handler->m_index[table->get_index_no()],
key, map);
}
else
{
for (uint ix = 0; ix < key_fields; ix++)
{
map[ix]= ix;
}
}
const Item* join_items[ndb_pushed_join::MAX_LINKED_KEYS+1];
collect_key_refs(table,join_items);
const KEY_PART_INFO *key_part= key->key_part;
for (uint i= 0; i < key_fields; i++, key_part++)
{
const Item* const item= join_items[i];
op_key[map[i]]= NULL;
DBUG_ASSERT(item->const_item() == item->const_during_execution());
if (item->const_item())
{
/**
* Propagate Items constant value to Field containing the value of this
* key_part:
*/
Field* const field= key_part->field;
DBUG_ASSERT(!field->is_real_null());
const uchar* const ptr= (field->real_type() == MYSQL_TYPE_VARCHAR)
? field->ptr + ((Field_varstring*)field)->length_bytes
: field->ptr;
op_key[map[i]]= m_builder->constValue(ptr, field->data_length());
}
else
{
DBUG_ASSERT(item->type() == Item::FIELD_ITEM);
const Item_field* const field_item= static_cast<const Item_field*>(item);
const uint referred_table_no= get_table_no(field_item);
if (m_join_scope.contain(referred_table_no))
{
// Locate the parent operation for this 'join_items[]'.
// May refer any of the preceding parent tables
const NdbQueryOperationDef* const parent_op= m_tables[referred_table_no].m_op;
DBUG_ASSERT(parent_op != NULL);
// TODO use field_index ??
op_key[map[i]]= m_builder->linkedValue(parent_op,
field_item->field_name);
}
else
{
DBUG_ASSERT(m_const_scope.contain(referred_table_no));
// Outside scope of join plan, Handle as parameter as its value
// will be known when we are ready to execute this query.
if (unlikely(m_fld_refs >= ndb_pushed_join::MAX_REFERRED_FIELDS))
{
DBUG_PRINT("info", ("Too many Field refs ( >= MAX_REFERRED_FIELDS) "
"encountered"));
DBUG_RETURN(-1); // TODO, handle gracefull -> continue?
}
m_referred_fields[m_fld_refs++]= field_item->field;
op_key[map[i]]= m_builder->paramValue();
}
}
if (unlikely(op_key[map[i]] == NULL))
{
DBUG_RETURN(-1);
}
}
op_key[key_fields]= NULL;
}
DBUG_RETURN(0);
} // ndb_pushed_builder_ctx::build_key()
int
ndb_pushed_builder_ctx::build_query()
{
DBUG_ENTER("build_query");
DBUG_PRINT("enter", ("Table %d as root is pushable", m_join_root->get_access_no()));
DBUG_EXECUTE("info", m_join_root->dbug_print(););
uint root_no= m_join_root->get_access_no();
DBUG_ASSERT(m_join_scope.contain(root_no));
if (m_builder == NULL)
{
m_builder= NdbQueryBuilder::create();
if (unlikely (m_builder==NULL))
{
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
}
}
for (uint tab_no= root_no; tab_no<m_plan.get_access_count(); tab_no++)
{
if (!m_join_scope.contain(tab_no))
continue;
const AQP::Table_access* const table= m_plan.get_table_access(tab_no);
const AQP::enum_access_type access_type= table->get_access_type();
const ha_ndbcluster* handler=
static_cast<ha_ndbcluster*>(table->get_table()->file);
const NdbQueryOperand* op_key[ndb_pushed_join::MAX_KEY_PART+1];
if (table->get_index_no() >= 0)
{
const int error= build_key(table, op_key);
if (unlikely(error))
DBUG_RETURN(error);
}
NdbQueryOptions options;
if (handler->m_cond)
{
NdbInterpretedCode code(handler->m_table);
if (handler->m_cond->generate_scan_filter(&code, NULL) != 0)
{
// ERR_RETURN(code.getNdbError()); // FIXME
}
options.setInterpretedCode(code);
}
if (table != m_join_root)
{
DBUG_ASSERT(m_tables[tab_no].m_parent!=MAX_TABLES);
const uint parent_no= m_tables[tab_no].m_parent;
const AQP::Table_access* parent= m_plan.get_table_access(parent_no);
if (!m_tables[tab_no].m_common_parents.contain(parent_no))
{
DBUG_ASSERT(m_tables[parent_no].m_op != NULL);
options.setParent(m_tables[parent_no].m_op);
}
if (table->get_join_type(parent) == AQP::JT_INNER_JOIN)
{
options.setMatchType(NdbQueryOptions::MatchNonNull);
}
}
const NdbQueryOperationDef* query_op= NULL;
if (ndbcluster_is_lookup_operation(access_type))
{
// Primary key access assumed
if (access_type == AQP::AT_PRIMARY_KEY ||
access_type == AQP::AT_MULTI_PRIMARY_KEY)
{
DBUG_PRINT("info", ("Operation is 'primary-key-lookup'"));
query_op= m_builder->readTuple(handler->m_table, op_key, &options);
}
else
{
DBUG_ASSERT(access_type == AQP::AT_UNIQUE_KEY);
DBUG_PRINT("info", ("Operation is 'unique-index-lookup'"));
const NdbDictionary::Index* const index
= handler->m_index[table->get_index_no()].unique_index;
DBUG_ASSERT(index);
query_op= m_builder->readTuple(index, handler->m_table, op_key, &options);
}
} // ndbcluster_is_lookup_operation()
/**
* AT_MULTI_MIXED may have 'ranges' which are pure single key lookups also.
* In our current implementation these are converted into range access in the
* pushed MRR implementation. However, the future plan is to build both
* RANGE and KEY pushable joins for these.
*/
else if (access_type == AQP::AT_ORDERED_INDEX_SCAN ||
access_type == AQP::AT_MULTI_MIXED)
{
DBUG_ASSERT(table->get_index_no() >= 0);
DBUG_ASSERT(handler->m_index[table->get_index_no()].index != NULL);
DBUG_PRINT("info", ("Operation is 'equal-range-lookup'"));
DBUG_PRINT("info", ("Creating scanIndex on index id:%d, name:%s",
table->get_index_no(),
handler->m_index[table->get_index_no()]
.index->getName()));
const NdbQueryIndexBound bounds(op_key);
query_op= m_builder->scanIndex(handler->m_index[table->get_index_no()].index,
handler->m_table, &bounds, &options);
}
else if (access_type == AQP::AT_TABLE_SCAN)
{
DBUG_PRINT("info", ("Operation is 'table scan'"));
query_op= m_builder->scanTable(handler->m_table, &options);
}
else
{
DBUG_ASSERT(false);
}
if (unlikely(!query_op))
DBUG_RETURN(-1);
m_tables[tab_no].m_op= query_op;
} // for (join_cnt= m_join_root->get_access_no(); join_cnt<plan.get_access_count(); join_cnt++)
DBUG_RETURN(0);
} // ndb_pushed_builder_ctx::build_query()
/**
* Fill in ix_map[] to map from KEY_PART_INFO[] order into
* primary key / unique key order of key fields.
*/
void
ndbcluster_build_key_map(const NDBTAB* table, const NDB_INDEX_DATA& index,
const KEY *key_def,
uint ix_map[])
{
uint ix;
if (index.unique_index_attrid_map) // UNIQUE_ORDERED_INDEX or UNIQUE_INDEX
{
for (ix = 0; ix < key_def->user_defined_key_parts; ix++)
{
ix_map[ix]= index.unique_index_attrid_map[ix];
}
}
else // Primary key does not have a 'unique_index_attrid_map'
{
KEY_PART_INFO *key_part;
uint key_pos= 0;
int columnnr= 0;
assert (index.type == PRIMARY_KEY_ORDERED_INDEX || index.type == PRIMARY_KEY_INDEX);
for (ix = 0, key_part= key_def->key_part; ix < key_def->user_defined_key_parts; ix++, key_part++)
{
// As NdbColumnImpl::m_keyInfoPos isn't available through
// NDB API we have to calculate it ourself, else we could:
// ix_map[ix]= table->getColumn(key_part->fieldnr-1)->m_impl.m_keyInfoPos;
if (key_part->fieldnr < columnnr)
{
// PK columns are not in same order as the columns are defined in the table,
// Restart PK search from first column:
key_pos=0;
columnnr= 0;
}
while (columnnr < key_part->fieldnr-1)
{
if (table->getColumn(columnnr++)->getPrimaryKey())
key_pos++;
}
assert(table->getColumn(columnnr)->getPrimaryKey());
ix_map[ix]= key_pos;
columnnr++;
key_pos++;
}
}
} // ndbcluster_build_key_map