polardbxengine/storage/ndb/test/tools/spj_sanity_test.cpp

1256 lines
37 KiB
C++

/*
Copyright (c) 2011, 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, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
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, version 2.0, 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
*/
#include <new>
#include <mysql.h>
#include <mysqld_error.h>
#include <ndb_global.h>
#include <ndb_opts.h>
#include <NDBT.hpp>
#include <NdbApi.hpp>
#include "../../src/ndbapi/NdbQueryBuilder.hpp"
#include "../../src/ndbapi/NdbQueryOperation.hpp"
/* TODO:
- RecAttr and setResultRowBuff result retrieval.
- Parameter operands.
- Add another table type (e.g. with CHAR() fields.)
*/
/* Query-related error codes. Used for negative testing. */
#define QRY_REQ_ARG_IS_NULL 4800
#define QRY_TOO_FEW_KEY_VALUES 4801
#define QRY_TOO_MANY_KEY_VALUES 4802
#define QRY_OPERAND_HAS_WRONG_TYPE 4803
#define QRY_CHAR_OPERAND_TRUNCATED 4804
#define QRY_NUM_OPERAND_RANGE 4805
#define QRY_MULTIPLE_PARENTS 4806
#define QRY_UNKONWN_PARENT 4807
#define QRY_UNKNOWN_COLUMN 4808
#define QRY_UNRELATED_INDEX 4809
#define QRY_WRONG_INDEX_TYPE 4810
#define QRY_OPERAND_ALREADY_BOUND 4811
#define QRY_DEFINITION_TOO_LARGE 4812
#define QRY_SEQUENTIAL_SCAN_SORTED 4813
#define QRY_RESULT_ROW_ALREADY_DEFINED 4814
#define QRY_HAS_ZERO_OPERATIONS 4815
#define QRY_IN_ERROR_STATE 4816
#define QRY_ILLEGAL_STATE 4817
#define QRY_WRONG_OPERATION_TYPE 4820
#define QRY_SCAN_ORDER_ALREADY_SET 4821
#define QRY_PARAMETER_HAS_WRONG_TYPE 4822
#define QRY_CHAR_PARAMETER_TRUNCATED 4823
#define QRY_MULTIPLE_SCAN_SORTED 4824
#define QRY_BATCH_SIZE_TOO_SMALL 4825
namespace SPJSanityTest{
static void resetError(const NdbError& err)
{
new (&const_cast<NdbError&>(err)) NdbError;
}
class IntField{
public:
static const char* getType(){
return "INT";
}
IntField(int i=0):
m_val(i)
{}
const char* toStr(char* buff) const {
sprintf(buff, "%d", m_val);
return buff;
}
int compare(const IntField& other) const{
if (m_val > other.m_val)
return 1;
else if (m_val == other.m_val)
return 0;
else
return -1;
}
Uint64 getValue() const{
return m_val;
}
Uint32 getSize() const{
return sizeof m_val;
}
private:
uint m_val;
};
class StrField{
public:
static const char* getType(){
return "VARCHAR(10)";
}
StrField(int i=0):
m_len(6){
// bzero(m_val, sizeof m_val);
sprintf(m_val, "c%5d", i);
}
const char* toStr(char* buff) const {
sprintf(buff, "'%s'", getValue());
return buff;
}
int compare(const StrField& other) const{
return strcmp(getValue(), other.getValue());
}
const char* getValue() const{
m_val[m_len] = '\0';
return m_val;
}
Uint32 getSize() const{
m_val[m_len] = '\0';
return strlen(m_val);
}
private:
Uint8 m_len;
mutable char m_val[10];
};
/* Key class.*/
template <typename FieldType>
class GenericKey{
public:
static const int size = 2;
FieldType m_values[size];
NdbConstOperand* makeConstOperand(NdbQueryBuilder& builder,
int fieldNo) const {
require(fieldNo<size);
//return builder.constValue(m_values[fieldNo]);
return builder.constValue(m_values[fieldNo].getValue());
}
};
/* Concrete Row class.*/
template <typename FieldType>
class GenericRow{
public:
static const int size = 4;
FieldType m_values[size];
explicit GenericRow<FieldType>(int rowNo){
/* Attribute values are chosen such that rows are sorted on
* all attribtes, and that any pair of consecutive columns can be
* used as a foreign key to the table itself.*/
for(int i = 0; i<size; i++){
m_values[i] = FieldType(i+rowNo);
}
}
static const char *getType(int colNo){
//return "INT";
return FieldType::getType();
}
static void makeSQLValues(char* buffer, int rowNo){
const GenericRow<FieldType> row(rowNo);
sprintf(buffer, "values(");
char* tail = buffer+strlen(buffer);
for(int i = 0; i<size; i++){
char tmp[11];
if(i<size-1){
// sprintf(tail, "%d,", row.m_values[i].toStr(tmp));
sprintf(tail, "%s,", row.m_values[i].toStr(tmp));
}else{
sprintf(tail, "%s)", row.m_values[i].toStr(tmp));
}
tail = buffer+strlen(buffer);
}
}
GenericKey<FieldType> getPrimaryKey() const;
GenericKey<FieldType> getIndexKey() const;
GenericKey<FieldType> getForeignKey(int keyNo) const;
void makeLessThanCond(NdbScanFilter& scanFilter){
//require(scanFilter.lt(0, m_values[0].getValue())==0);
require(scanFilter.cmp(NdbScanFilter::COND_LT, 0, m_values, m_values[0].getSize())==0);
}
/** Get the row column number that corresponds to the n'th column
* of the index.*/
static int getIndexKeyColNo(int indexCol);
/** Get the row column number that corresponds to the n'th column
* of the m'th foreign key..*/
static int getForeignKeyColNo(int keyNo, int keyCol);
};
template <typename FieldType>
GenericKey<FieldType> GenericRow<FieldType>::getPrimaryKey() const {
GenericKey<FieldType> key;
for(int i = 0; i<GenericKey<FieldType>::size; i++){
key.m_values[i] = m_values[i];
}
return key;
}
template <typename FieldType>
GenericKey<FieldType> GenericRow<FieldType>::getIndexKey() const {
return getForeignKey(1);
}
template <typename FieldType>
GenericKey<FieldType> GenericRow<FieldType>::getForeignKey(int keyNo) const {
require(keyNo<=1);
GenericKey<FieldType> key;
for(int i = 0; i<GenericKey<FieldType>::size; i++){
key.m_values[i] = m_values[getForeignKeyColNo(keyNo,i)];
}
return key;
}
template <typename FieldType>
int GenericRow<FieldType>::getIndexKeyColNo(int indexCol){
return getForeignKeyColNo(1, indexCol);
}
template <typename FieldType>
int GenericRow<FieldType>::getForeignKeyColNo(int keyNo, int keyCol){
require(keyNo<GenericRow<FieldType>::size-GenericKey<FieldType>::size);
require(keyCol<GenericKey<FieldType>::size);
return size-GenericKey<FieldType>::size-keyNo+keyCol;
}
template <typename FieldType>
static bool operator==(const GenericRow<FieldType>& a, const GenericRow<FieldType>& b){
for(int i = 0; i<GenericRow<FieldType>::size; i++){
if(a.m_values[i].compare(b.m_values[i]) != 0){
return false;
}
}
return true;
}
template <typename FieldType>
static bool operator==(const GenericKey<FieldType>& a, const GenericKey<FieldType>& b){
for(int i = 0; i<GenericKey<FieldType>::size; i++){
if(a.m_values[i].compare(b.m_values[i]) != 0){
return false;
}
}
return true;
}
/** Returns true if key of a <= key of b.*/
template <typename FieldType>
static bool lessOrEqual(const GenericRow<FieldType>& a, const GenericRow<FieldType>& b){
for(int i = 0; i<GenericKey<FieldType>::size; i++){
if(a.m_values[i].compare(b.m_values[i]) == 1){
return false;
}
}
return true;
}
template <typename FieldType>
static NdbOut& operator<<(NdbOut& out, const GenericRow<FieldType>& row){
char buff[11];
out << "{";
for(int i = 0; i<GenericRow<FieldType>::size; i++){
out << row.m_values[i].toStr(buff);
if(i<GenericRow<FieldType>::size-1){
out << ", ";
}
}
out << "}";
return out;
}
//typedef GenericRow<IntField> Row;
//typedef GenericKey<IntField> Key;
typedef GenericRow<StrField> Row;
typedef GenericKey<StrField> Key;
static const char* colName(int colNo){
static const char* names[] = {
"c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10"
};
require(static_cast<unsigned int>(colNo)< sizeof names/sizeof names[0]);
return names[colNo];
};
static void printMySQLError(MYSQL& mysql, const char* before=NULL){
if(before!=NULL){
ndbout << before;
}
ndbout << mysql_error(&mysql) << endl;
exit(-1);
}
static void mySQLExec(MYSQL& mysql, const char* stmt){
ndbout << stmt << ";" << endl;
if(mysql_query(&mysql, stmt) != 0){
ndbout << "Error executing '" << stmt << "' : ";
printMySQLError(mysql);
}
}
class Query;
/** Class representing a single NdbQueryOperation. 'Row'
* is a template argument, to allow different table defintions.*/
class Operation{
public:
explicit Operation(class Query& query, Operation* parent);
virtual ~Operation(){}
//protected: FIXME
public:
friend class Query;
/** Enclosing NdbQuery.*/
Query& m_query;
/** Optional parent operation.*/
const Operation* m_parent;
Vector<Operation*> m_children;
const NdbQueryOperationDef* m_operationDef;
// For now, only setResultRowRef() style result retrieval is tested.
union
{
const Row* m_resultPtr;
// Use union to avoid strict-aliasing problems.
const char* m_resultCharPtr;
};
// Corresponds to NdbQueryOperationDef operation numbering
Uint32 m_operationId;
// Number among siblings.
const Uint32 m_childNo;
/** Check that result of this op and descendans is ok.*/
void verifyRow();
/** Check that result of this op is ok.*/
virtual void verifyOwnRow() = 0;
/** Build operation definition.*/
virtual void buildThis(NdbQueryBuilder& builder,
const NdbDictionary::Table& tab) = 0;
/** Build this and descendants.*/
void build(NdbQueryBuilder& builder,
const NdbDictionary::Table& tab);
/** Set up result retrieval before execution.*/
virtual void submit()=0;
void compareRows(const char* text,
const Row* expected,
const Row* actual) const;
};
class Query{
public:
explicit Query(Ndb& ndb);
~Query(){
m_builder->destroy();
if (m_queryDef != NULL)
{
m_queryDef->destroy();
}
}
/** Build query definition.*/
void build(const NdbDictionary::Table& tab, int tableSize);
/** Execute within transaction.*/
void submit(NdbTransaction& transaction);
void submitOperation(Operation& operation) const;
void setRoot(Operation& root){ m_root = &root;}
NdbQuery::NextResultOutcome nextResult(){
return m_query->nextResult(true, false);
}
/** Verify current row for all operations.*/
void verifyRow() const {
m_root->verifyRow();
}
Uint32 allocOperationId(){
return m_operationCount++;
}
NdbQueryOperation* getOperation(Uint32 ident) const {
return m_query->getQueryOperation(ident);
}
int getTableSize() const {
return m_tableSize;
}
const NdbRecord* getNdbRecord() const {
return m_ndbRecord;
}
const NdbDictionary::Dictionary* getDictionary() const {
return m_ndb.getDictionary();
}
void close(bool forceSend = false){
m_query->close(forceSend);
}
private:
Ndb& m_ndb;
NdbQueryBuilder* const m_builder;
Operation* m_root;
const NdbQueryDef* m_queryDef;
NdbQuery* m_query;
Uint32 m_operationCount;
int m_tableSize;
const NdbRecord* m_ndbRecord;
};
class LookupOperation: public Operation{
public:
explicit LookupOperation(Query& query,
Operation* parent = NULL);
virtual void verifyOwnRow();
/** Set up result retrieval before execution.*/
virtual void submit();
protected:
virtual void buildThis(NdbQueryBuilder& builder,
const NdbDictionary::Table& tab);
};
class IndexLookupOperation: public Operation{
public:
explicit IndexLookupOperation(Query& query,
const char* indexName,
Operation* parent = NULL);
virtual void verifyOwnRow();
/** Set up result retrieval before execution.*/
virtual void submit();
protected:
virtual void buildThis(NdbQueryBuilder& builder,
const NdbDictionary::Table& tab);
private:
const char* const m_indexName;
};
class TableScanOperation: public Operation{
public:
explicit TableScanOperation(Query& query, int lessThanRow=-1);
virtual ~TableScanOperation() {
delete[] m_rowFound;
}
virtual void verifyOwnRow();
/** Set up result retrieval before execution.*/
virtual void submit();
protected:
virtual void buildThis(NdbQueryBuilder& builder,
const NdbDictionary::Table& tab);
private:
bool* m_rowFound;
int m_lessThanRow;
};
class IndexScanOperation: public Operation{
public:
explicit IndexScanOperation(Query& query,
const char* indexName,
int lowerBoundRowNo,
int upperBoundRowNo,
NdbQueryOptions::ScanOrdering ordering);
virtual ~IndexScanOperation() {
delete[] m_rowFound;
}
virtual void verifyOwnRow();
/** Set up result retrieval before execution.*/
virtual void submit();
protected:
virtual void buildThis(NdbQueryBuilder& builder,
const NdbDictionary::Table& tab);
private:
const char* const m_indexName;
/** Number of table row from which get key to use as lower bound.*/
const int m_lowerBoundRowNo;
/** Number of table row from which get key to use as upper bound.*/
const int m_upperBoundRowNo;
/** An entry per row. True if row has been seen in the result stream.*/
bool* m_rowFound;
/** Ordering of results.*/
NdbQueryOptions::ScanOrdering m_ordering;
/** Previous row, for verifying ordering.*/
Row m_previousRow;
/** True from the second row and onwards.*/
bool m_hasPreviousRow;
};
// Query methods.
Query::Query(Ndb& ndb):
m_ndb(ndb),
m_builder(NdbQueryBuilder::create()),
m_root(NULL),
m_queryDef(NULL),
m_query(NULL),
m_operationCount(0),
m_tableSize(-1),
m_ndbRecord(NULL)
{
require(m_builder != NULL);
}
void Query::build(const NdbDictionary::Table& tab, int tableSize){
m_tableSize = tableSize;
m_root->build(*m_builder, tab);
m_queryDef = m_builder->prepare();
m_ndbRecord = tab.getDefaultRecord();
}
void Query::submit(NdbTransaction& transaction){
m_query = transaction.createQuery(m_queryDef);
require(m_query!=NULL);
submitOperation(*m_root);
}
void Query::submitOperation(Operation& operation) const{
// Do a depth first traversal of the operations graph.
operation.submit();
for(Uint32 i = 0; i<operation.m_children.size(); i++){
submitOperation(*operation.m_children[i]);
}
}
// Operation methods.
Operation::Operation(class Query& query,
Operation* parent):
m_query(query),
m_parent(parent),
m_operationDef(NULL),
m_resultPtr(NULL),
m_childNo(parent == NULL ? 0 : parent->m_children.size())
{
if(parent==NULL){
query.setRoot(*this);
}else{
parent->m_children.push_back(this);
}
}
void Operation::build(NdbQueryBuilder& builder,
const NdbDictionary::Table& tab){
m_operationId = m_query.allocOperationId();
buildThis(builder, tab);
require(builder.getNdbError().code==0);
for(Uint32 i = 0; i<m_children.size(); i++){
m_children[i]->build(builder, tab);
}
}
void Operation::verifyRow(){
verifyOwnRow();
for(Uint32 i = 0; i<m_children.size(); i++){
m_children[i]->verifyRow();
}
}
typedef const char* constCharPtr;
void Operation::compareRows(const char* text,
const Row* expected,
const Row* actual) const{
if(expected==NULL){
if(actual==NULL){
ndbout << text << " operationId=" << m_operationId
<< " expected NULL and got it." << endl;
}else{
ndbout << text << " operationId=" << m_operationId
<< " expected NULL but got." << *actual
<< endl;
require(false);
}
}else{
if(actual==NULL){
ndbout << text << " operationId=" << m_operationId
<< " expected: " << *expected
<< " but got NULL." << endl;
require(false);
}else{
ndbout << text << " operationId=" << m_operationId
<< " expected: " << *expected;
if(*expected == *actual){
ndbout << " and got it." << endl;
}else{
ndbout << " but got: " << *actual;
require(false);
}
}
}
};
// LookupOperation methods.
LookupOperation
::LookupOperation(Query& query,
Operation* parent):
Operation(query, parent){
}
void LookupOperation::buildThis(NdbQueryBuilder& builder,
const NdbDictionary::Table& tab){
NdbQueryOperand* keyOperands[Key::size+2];
if(m_parent==NULL){
const Key key = Row(0).getPrimaryKey();
for(int i = 0; i<Key::size; i++){
keyOperands[i] = key.makeConstOperand(builder, i);
}
}else{
// Negative testing
require(builder.linkedValue(m_parent->m_operationDef,
"unknown_col") == NULL);
require(builder.getNdbError().code == QRY_UNKNOWN_COLUMN);
for(int i = 0; i<Key::size; i++){
keyOperands[i] =
builder.linkedValue(m_parent->m_operationDef,
colName(Row::getForeignKeyColNo(
m_childNo, i)));
require(keyOperands[i]!=NULL);
/*Row::makeLinkedKey(builder, keyOperands,
Operation::m_parent->m_operationDef,
Operation::m_childNo);*/
}
}
// Negative testing
keyOperands[Key::size] = keyOperands[0];
keyOperands[Key::size+1] = NULL;
require(builder.readTuple(&tab, keyOperands)== NULL);
require(builder.getNdbError().code == QRY_TOO_MANY_KEY_VALUES);
resetError(builder.getNdbError());
keyOperands[Key::size] = NULL;
m_operationDef = builder.readTuple(&tab, keyOperands);
require(m_operationDef != NULL);
// Negative testing
keyOperands[Key::size-1] = builder.constValue(0x1fff1fff);
require(keyOperands[Key::size-1] != NULL);
require(builder.readTuple(&tab, keyOperands) == NULL);
require(builder.getNdbError().code == QRY_OPERAND_HAS_WRONG_TYPE);
// Negative testing
keyOperands[Key::size-1] = NULL;
require(builder.readTuple(&tab, keyOperands) == NULL);
require(builder.getNdbError().code == QRY_TOO_FEW_KEY_VALUES);
resetError(builder.getNdbError());
}
void LookupOperation::submit(){
NdbQueryOperation* queryOp
= m_query.getOperation(m_operationId);
// Negative testing
require(queryOp->setResultRowRef(NULL,
m_resultCharPtr,
NULL) == -1);
require(queryOp->getQuery().getNdbError().code ==
QRY_REQ_ARG_IS_NULL);
require(
queryOp->setOrdering(NdbQueryOptions::ScanOrdering_ascending) == -1);
require(
queryOp->getQuery().getNdbError().code == QRY_WRONG_OPERATION_TYPE);
require(queryOp->setResultRowRef(m_query.getNdbRecord(),
m_resultCharPtr,
NULL) == 0);
// Negative testing
require(queryOp->setResultRowRef(m_query.getNdbRecord(),
m_resultCharPtr,
NULL) == -1);
require(queryOp->getQuery().getNdbError().code ==
QRY_RESULT_ROW_ALREADY_DEFINED);
}
void LookupOperation::verifyOwnRow(){
if(m_parent==NULL){
const Row expected(0);
compareRows("lookup root operation",
&expected,
m_resultPtr);
}else{
NdbQueryOperation* queryOp
= m_query
.getOperation(m_operationId);
if(!queryOp->getParentOperation(0)->isRowNULL()){
const Key key =
m_parent->m_resultPtr
->getForeignKey(m_childNo);
bool found = false;
for(int i = 0; i<m_query.getTableSize(); i++){
const Row row(i);
if(row.getPrimaryKey() == key){
found = true;
compareRows("lookup child operation",
&row,
m_resultPtr);
}
}
if(!found && !queryOp->isRowNULL()){
compareRows("lookup child operation",
NULL,
m_resultPtr);
}
}
}
}
// IndexLookupOperation methods.
IndexLookupOperation
::IndexLookupOperation(Query& query,
const char* indexName,
Operation* parent):
Operation(query, parent),
m_indexName(indexName){
}
void IndexLookupOperation
::buildThis(NdbQueryBuilder& builder,
const NdbDictionary::Table& tab){
const NdbDictionary::Dictionary* const dict
= m_query.getDictionary();
char fullName[200];
sprintf(fullName, "%s$unique", m_indexName);
const NdbDictionary::Index* const index
= dict->getIndex(fullName, tab.getName());
require(index!=NULL);
NdbQueryOperand* keyOperands[Key::size+1];
if(m_parent==NULL){
const Key key = Row(0).getIndexKey();
for(int i = 0; i<Key::size; i++){
keyOperands[i] = key.makeConstOperand(builder, i);
}
}else{
for(int i = 0; i<Key::size; i++){
keyOperands[i] =
builder.linkedValue(m_parent->m_operationDef,
colName(Row::getForeignKeyColNo(
m_childNo, i)));
require(keyOperands[i]!=NULL);
}
/*Row::makeLinkedKey(builder, keyOperands,
Operation::m_parent->m_operationDef,
Operation::m_childNo);*/
}
keyOperands[Key::size] = NULL;
m_operationDef = builder.readTuple(index, &tab, keyOperands);
// Negative testing
const NdbDictionary::Index* const orderedIndex
= dict->getIndex(m_indexName, tab.getName());
require(orderedIndex != NULL);
require(builder.readTuple(orderedIndex, &tab, keyOperands) == NULL);
require(builder.getNdbError().code == QRY_WRONG_INDEX_TYPE);
resetError(builder.getNdbError());
}
void IndexLookupOperation::submit(){
NdbQueryOperation* queryOp
= m_query.getOperation(m_operationId);
queryOp->setResultRowRef(m_query.getNdbRecord(),
m_resultCharPtr,
NULL);
}
void IndexLookupOperation::verifyOwnRow(){
if(m_parent==NULL){
const Row expected(0);
compareRows("index lookup root operation",
&expected,
m_resultPtr);
}else{
NdbQueryOperation* queryOp
= m_query
.getOperation(m_operationId);
if(!queryOp->getParentOperation(0)->isRowNULL()){
const Key key =
m_parent->m_resultPtr
->getForeignKey(m_childNo);
bool found = false;
for(int i = 0; i<m_query.getTableSize(); i++){
const Row row(i);
if(row.getIndexKey() == key){
found = true;
compareRows("index lookup child operation",
&row,
m_resultPtr);
}
}
if(!found && !queryOp->isRowNULL()){
compareRows("index lookup child operation",
NULL,
m_resultPtr);
}
}
}
}
// TableScanOperation methods.
TableScanOperation
::TableScanOperation(Query& query, int lessThanRow):
Operation(query, NULL),
m_rowFound(NULL),
m_lessThanRow(lessThanRow){
}
void TableScanOperation::buildThis(NdbQueryBuilder& builder,
const NdbDictionary::Table& tab){
m_operationDef = builder.scanTable(&tab);
m_rowFound = new bool[m_query.getTableSize()];
for(int i = 0; i<m_query.getTableSize(); i++){
m_rowFound[i] = false;
}
}
void TableScanOperation::submit(){
NdbQueryOperation* queryOp
= m_query.getOperation(m_operationId);
queryOp->setResultRowRef(m_query.getNdbRecord(),
m_resultCharPtr,
NULL);
if(m_lessThanRow!=-1){
NdbInterpretedCode code(queryOp->getQueryOperationDef().getTable());
NdbScanFilter filter(&code);
require(filter.begin()==0);
Row(m_lessThanRow).makeLessThanCond(filter);
require(filter.end()==0);
require(queryOp->setInterpretedCode(code)==0);
}
}
void TableScanOperation::verifyOwnRow(){
bool found = false;
const int upperBound =
m_lessThanRow==-1 ?
m_query.getTableSize() :
m_lessThanRow;
for(int i = 0; i<upperBound; i++){
//const Row row(i);
if(Row(i) == *m_resultPtr){
found = true;
if(m_rowFound[i]){
ndbout << "Root table scan operation: "
<< *m_resultPtr
<< "appeared twice." << endl;
require(false);
}
m_rowFound[i] = true;
}
}
if(!found){
ndbout << "Root table scan operation. Unexpected row: "
<< *m_resultPtr << endl;
require(false);
}else{
ndbout << "Root table scan operation. Got row: "
<< *m_resultPtr
<< " as expected." << endl;
}
}
// IndexScanOperation methods.
IndexScanOperation
::IndexScanOperation(Query& query,
const char* indexName,
int lowerBoundRowNo,
int upperBoundRowNo,
NdbQueryOptions::ScanOrdering ordering):
Operation(query, NULL),
m_indexName(indexName),
m_lowerBoundRowNo(lowerBoundRowNo),
m_upperBoundRowNo(upperBoundRowNo),
m_ordering(ordering),
m_previousRow(0),
m_hasPreviousRow(false){
}
void IndexScanOperation::buildThis(NdbQueryBuilder& builder,
const NdbDictionary::Table& tab){
const NdbDictionary::Dictionary* const dict
= m_query.getDictionary();
const NdbDictionary::Index* const index
= dict->getIndex(m_indexName, tab.getName());
require(index!=NULL);
const NdbQueryOperand* low[Key::size+1];
const NdbQueryOperand* high[Key::size+1];
// Code below assume that we use primary key index.
require(strcmp(m_indexName, "PRIMARY")==0);
/* Tables are alway sorted on all columns. Using these bounds,
we therefore get m_upperBoundRowNo - m_lowerBoundRowNo +1 rows.*/
const Key& lowKey = *new Key(Row(m_lowerBoundRowNo).getPrimaryKey());
const Key& highKey = *new Key(Row(m_upperBoundRowNo).getPrimaryKey());
for(int i = 0; i<Key::size; i++){
low[i] = lowKey.makeConstOperand(builder, i);
high[i] = highKey.makeConstOperand(builder, i);
}
low[Key::size] = NULL;
high[Key::size] = NULL;
NdbQueryOptions options;
options.setOrdering(m_ordering);
const NdbQueryIndexBound bound(low, high);
const NdbQueryIndexScanOperationDef* opDef
= builder.scanIndex(index, &tab, &bound, &options);
m_operationDef = opDef;
require(m_operationDef!=NULL);
m_rowFound = new bool[m_query.getTableSize()];
for(int i = 0; i<m_query.getTableSize(); i++){
m_rowFound[i] = false;
}
}
void IndexScanOperation::submit(){
NdbQueryOperation* queryOp
= m_query.getOperation(m_operationId);
queryOp->setResultRowRef(m_query.getNdbRecord(),
m_resultCharPtr,
NULL);
// Negative testing.
if (m_ordering != NdbQueryOptions::ScanOrdering_unordered){
require(
queryOp->setOrdering(NdbQueryOptions::ScanOrdering_ascending) != 0);
require(
queryOp->getQuery().getNdbError().code == QRY_SCAN_ORDER_ALREADY_SET);
require(queryOp->setParallelism(1) != 0);
require(
queryOp->getQuery().getNdbError().code == QRY_SEQUENTIAL_SCAN_SORTED);
}
}
void IndexScanOperation::verifyOwnRow(){
bool found = false;
for(int i = m_lowerBoundRowNo; i<=m_upperBoundRowNo; i++){
const Row row(i);
if(row == *m_resultPtr){
found = true;
if(m_rowFound[i]){
ndbout << "Root index scan operation: "
<< *m_resultPtr
<< "appeared twice." << endl;
require(false);
}
m_rowFound[i] = true;
}
}
if(!found){
ndbout << "Root index scan operation. Unexpected row: "
<< *m_resultPtr << endl;
require(false);
}else{
if(m_hasPreviousRow){
switch(m_ordering){
case NdbQueryOptions::ScanOrdering_ascending:
if(!lessOrEqual(m_previousRow, *m_resultPtr)){
ndbout << "Error in result ordering. Did not expect row "
<< *m_resultPtr
<< " now." << endl;
require(false);
}
break;
case NdbQueryOptions::ScanOrdering_descending:
if(lessOrEqual(m_previousRow, *m_resultPtr)){
ndbout << "Error in result ordering. Did not expect row "
<< *m_resultPtr
<< " now." << endl;
require(false);
}
break;
case NdbQueryOptions::ScanOrdering_unordered:
break;
default:
require(false);
}
}
m_hasPreviousRow = true;
m_previousRow = *m_resultPtr;
ndbout << "Root index scan operation. Got row: "
<< *m_resultPtr
<< " as expected." << endl;
}
}
// Misc. functions.
/** Make and populate SQL table.*/
void makeTable(MYSQL& mysql, const char* name, int rowCount){
char cmd[500];
char piece[500];
sprintf(cmd, "drop table if exists %s", name);
mySQLExec(mysql, cmd);
sprintf(cmd, "create table %s (\n", name);
for(int i = 0; i<Row::size; i++){
sprintf(piece, " %s %s NOT NULL,\n", colName(i), Row::getType(i));
strcat(cmd, piece);
}
strcat(cmd, " PRIMARY KEY(");
for(int i = 0; i<Key::size; i++){
strcat(cmd, colName(i));
if(i<Key::size - 1){
strcat(cmd, ",");
}else{
strcat(cmd, "),\n");
}
}
strcat(cmd, " UNIQUE KEY UIX (");
for(int i = 0; i<Key::size; i++){
strcat(cmd, colName(Row::getIndexKeyColNo(i)));
if(i<Key::size - 1){
strcat(cmd, ",");
}else{
strcat(cmd, "))\n");
}
}
strcat(cmd, "ENGINE=NDB");
mySQLExec(mysql, cmd);
for(int i = 0; i<rowCount; i++){
Row::makeSQLValues(piece, i);
sprintf(cmd, "insert into %s %s", name, piece);
mySQLExec(mysql, cmd);
}
};
/* Execute a test for a give operation graph.*/
void runCase(MYSQL& mysql,
Ndb& ndb,
Query& query,
const char* tabName,
int tabSize,
int rowCount){
// Populate test table.
makeTable(mysql, tabName, tabSize);
NdbDictionary::Dictionary* const dict = ndb.getDictionary();
const NdbDictionary::Table* const tab = dict->getTable(tabName);
require(tab!=NULL);
// Build generic query definition.
query.build(*tab, tabSize);
NdbTransaction* trans = ndb.startTransaction();
require(trans!=NULL);
// instantiate query within transaction.
query.submit(*trans);
require(trans->execute(NoCommit)==0);
// Verify each row and total number of rows.
for(int i = 0; i<rowCount; i++){
require(query.nextResult() == NdbQuery::NextResult_gotRow);
query.verifyRow();
if(false && i>3){
// Enable to test close of incomplete scan.
query.close();
ndb.closeTransaction(trans);
return;
}
}
require(query.nextResult() == NdbQuery::NextResult_scanComplete);
ndb.closeTransaction(trans);
}
/** Run a set of test cases.*/
void runTestSuite(MYSQL& mysql, Ndb& ndb){
for(int caseNo = 0; caseNo<7; caseNo++){
ndbout << endl << "Running test case " << caseNo << endl;
char tabName[20];
sprintf(tabName, "t%d", caseNo);
Query query(ndb);
switch(caseNo){
case 0:
{
LookupOperation root(query);
LookupOperation child(query, &root);
LookupOperation child2(query, &root);
runCase(mysql, ndb, query, tabName, 1, 1);
}
break;
case 1:
{
IndexLookupOperation root(query, "UIX");
IndexLookupOperation child(query, "UIX", &root);
runCase(mysql, ndb, query, tabName, 5, 1);
}
break;
case 2:
{
IndexScanOperation root(query, "PRIMARY", 2, 4,
NdbQueryOptions::ScanOrdering_unordered);
LookupOperation child(query, &root);
IndexLookupOperation child2(query, "UIX", &child);
LookupOperation child3(query, &child);
runCase(mysql, ndb, query, tabName, 5, 3);
}
break;
case 3:
{
TableScanOperation root(query);
LookupOperation child(query, &root);
runCase(mysql, ndb, query, tabName, 5, 5);
}
break;
case 4:
{
TableScanOperation root(query);
IndexLookupOperation child1(query, "UIX", &root);
LookupOperation child2(query, &child1);
IndexLookupOperation child3(query, "UIX", &child2);
LookupOperation child1_2(query, &root);
LookupOperation child2_2(query, &child1_2);
runCase(mysql, ndb, query, tabName, 10, 10);
}
break;
case 5:
{
IndexScanOperation root(query, "PRIMARY", 0, 10,
NdbQueryOptions::ScanOrdering_descending);
LookupOperation child(query, &root);
runCase(mysql, ndb, query, tabName, 10, 10);
}
break;
case 6:
{
TableScanOperation root(query, 3);
LookupOperation child(query, &root);
runCase(mysql, ndb, query, tabName, 5, 3);
}
break;
#if 0
default:
//case 6:
{
IndexScanOperation root(query, "PRIMARY", 0, 1000,
NdbQueryOptions::ScanOrdering_descending);
LookupOperation child(query, &root);
runCase(mysql, ndb, query, tabName, 10*(caseNo-6), 10*(caseNo-6));
}
break;
#endif
}
}
}
};
using namespace SPJSanityTest;
int main(int argc, char* argv[]){
if(argc!=4){
ndbout << "Usage: " << argv[0]
<< " <mysql IP address> <mysql port> <cluster connect string>"
<< endl;
return NDBT_ProgramExit(NDBT_FAILED);
}
const char* const host=argv[1];
const int port = atoi(argv[2]);
const char* const connectString = argv[3];
NDB_INIT(argv[0]);
MYSQL mysql;
require(mysql_init(&mysql));
if(!mysql_real_connect(&mysql, host, "root", "", "",
port, NULL, 0)){
printMySQLError(mysql, "mysql_real_connect() failed:");
return NDBT_ProgramExit(NDBT_FAILED);
}
mySQLExec(mysql, "create database if not exists CK_DB");
mySQLExec(mysql, "use CK_DB");
{
Ndb_cluster_connection con(connectString);
if(con.connect(12, 5, 1) != 0){
ndbout << "Unable to connect to management server." << endl;
return NDBT_ProgramExit(NDBT_FAILED);
}
int res = con.wait_until_ready(30,30);
if (res != 0){
ndbout << "Cluster nodes not ready in 30 seconds." << endl;
return NDBT_ProgramExit(NDBT_FAILED);
}
Ndb ndb(&con, "CK_DB");
if(ndb.init() != 0){
ERR(ndb.getNdbError());
return NDBT_ProgramExit(NDBT_FAILED);
}
runTestSuite(mysql, ndb);
} // Must call ~Ndb_cluster_connection() before ndb_end().
ndb_end(0);
return 0;
}
// Explicit template instantiations.
template class Vector<Operation*>;
template class GenericRow<IntField>;
template class GenericKey<IntField>;
template class GenericRow<StrField>;
template class GenericKey<StrField>;