MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
spj_sanity_test.cpp
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 */
17 
18 #include <new>
19 #include <assert.h>
20 #include <mysql.h>
21 #include <mysqld_error.h>
22 
23 #include <ndb_global.h>
24 #include <ndb_opts.h>
25 #include <NDBT.hpp>
26 #include <NdbApi.hpp>
27 #include "../../src/ndbapi/NdbQueryBuilder.hpp"
28 #include "../../src/ndbapi/NdbQueryOperation.hpp"
29 
30 
31 /* TODO:
32  - RecAttr and setResultRowBuff result retrieval.
33  - Parameter operands.
34  - Add another table type (e.g. with CHAR() fields.)
35 */
36 
37 #ifdef NDEBUG
38 // Some asserts have side effects, and there is no other error handling anyway.
39 #define ASSERT_ALWAYS(cond) if(!(cond)){abort();}
40 #else
41 #define ASSERT_ALWAYS assert
42 #endif
43 /* Query-related error codes. Used for negative testing. */
44 #define QRY_REQ_ARG_IS_NULL 4800
45 #define QRY_TOO_FEW_KEY_VALUES 4801
46 #define QRY_TOO_MANY_KEY_VALUES 4802
47 #define QRY_OPERAND_HAS_WRONG_TYPE 4803
48 #define QRY_CHAR_OPERAND_TRUNCATED 4804
49 #define QRY_NUM_OPERAND_RANGE 4805
50 #define QRY_MULTIPLE_PARENTS 4806
51 #define QRY_UNKONWN_PARENT 4807
52 #define QRY_UNKNOWN_COLUMN 4808
53 #define QRY_UNRELATED_INDEX 4809
54 #define QRY_WRONG_INDEX_TYPE 4810
55 #define QRY_OPERAND_ALREADY_BOUND 4811
56 #define QRY_DEFINITION_TOO_LARGE 4812
57 #define QRY_SEQUENTIAL_SCAN_SORTED 4813
58 #define QRY_RESULT_ROW_ALREADY_DEFINED 4814
59 #define QRY_HAS_ZERO_OPERATIONS 4815
60 #define QRY_IN_ERROR_STATE 4816
61 #define QRY_ILLEGAL_STATE 4817
62 #define QRY_WRONG_OPERATION_TYPE 4820
63 #define QRY_SCAN_ORDER_ALREADY_SET 4821
64 #define QRY_PARAMETER_HAS_WRONG_TYPE 4822
65 #define QRY_CHAR_PARAMETER_TRUNCATED 4823
66 #define QRY_MULTIPLE_SCAN_BRANCHES 4824
67 #define QRY_MULTIPLE_SCAN_SORTED 4825
68 
69 
70 namespace SPJSanityTest{
71 
72  static void resetError(const NdbError& err)
73  {
74  new (&const_cast<NdbError&>(err)) NdbError;
75  }
76 
77  class IntField{
78  public:
79  static const char* getType(){
80  return "INT";
81  }
82 
83  IntField(int i=0):
84  m_val(i)
85  {}
86 
87  const char* toStr(char* buff) const {
88  sprintf(buff, "%d", m_val);
89  return buff;
90  }
91 
92  int compare(const IntField& other) const{
93  if (m_val > other.m_val)
94  return 1;
95  else if (m_val == other.m_val)
96  return 0;
97  else
98  return -1;
99  }
100 
101  Uint64 getValue() const{
102  return m_val;
103  }
104 
105  Uint32 getSize() const{
106  return sizeof m_val;
107  }
108 
109  private:
110  uint m_val;
111  };
112 
113  class StrField{
114  public:
115  static const char* getType(){
116  return "VARCHAR(10)";
117  }
118 
119  StrField(int i=0):
120  m_len(6){
121  // bzero(m_val, sizeof m_val);
122  sprintf(m_val, "c%5d", i);
123  }
124 
125  const char* toStr(char* buff) const {
126  sprintf(buff, "'%s'", getValue());
127  return buff;
128  }
129 
130  int compare(const StrField& other) const{
131  return strcmp(getValue(), other.getValue());
132  }
133 
134  const char* getValue() const{
135  m_val[m_len] = '\0';
136  return m_val;
137  }
138 
139  Uint32 getSize() const{
140  m_val[m_len] = '\0';
141  return strlen(m_val);
142  }
143 
144  private:
145  Uint8 m_len;
146  mutable char m_val[10];
147  };
148 
149 
150  /* Key class.*/
151  template <typename FieldType>
152  class GenericKey{
153  public:
154  static const int size = 2;
155  FieldType m_values[size];
156 
157  NdbConstOperand* makeConstOperand(NdbQueryBuilder& builder,
158  int fieldNo) const {
159  ASSERT_ALWAYS(fieldNo<size);
160  //return builder.constValue(m_values[fieldNo]);
161  return builder.constValue(m_values[fieldNo].getValue());
162  }
163  };
164 
165  /* Concrete Row class.*/
166  template <typename FieldType>
167  class GenericRow{
168  public:
169  static const int size = 4;
170 
171  FieldType m_values[size];
172 
173  explicit GenericRow<FieldType>(int rowNo){
174  /* Attribute values are chosen such that rows are sorted on
175  * all attribtes, and that any pair of consecutive columns can be
176  * used as a foreign key to the table itself.*/
177  for(int i = 0; i<size; i++){
178  m_values[i] = FieldType(i+rowNo);
179  }
180  }
181 
182  static const char *getType(int colNo){
183  //return "INT";
184  return FieldType::getType();
185  }
186 
187  static void makeSQLValues(char* buffer, int rowNo){
188  const GenericRow<FieldType> row(rowNo);
189  sprintf(buffer, "values(");
190  char* tail = buffer+strlen(buffer);
191  for(int i = 0; i<size; i++){
192  char tmp[11];
193  if(i<size-1){
194  // sprintf(tail, "%d,", row.m_values[i].toStr(tmp));
195  sprintf(tail, "%s,", row.m_values[i].toStr(tmp));
196  }else{
197  sprintf(tail, "%s)", row.m_values[i].toStr(tmp));
198  }
199  tail = buffer+strlen(buffer);
200  }
201  }
202 
203  GenericKey<FieldType> getPrimaryKey() const;
204 
205  GenericKey<FieldType> getIndexKey() const;
206 
207  GenericKey<FieldType> getForeignKey(int keyNo) const;
208 
209  void makeLessThanCond(NdbScanFilter& scanFilter){
210  //ASSERT_ALWAYS(scanFilter.lt(0, m_values[0].getValue())==0);
211  ASSERT_ALWAYS(scanFilter.cmp(NdbScanFilter::COND_LT, 0, m_values, m_values[0].getSize())==0);
212  }
213 
216  static int getIndexKeyColNo(int indexCol);
217 
220  static int getForeignKeyColNo(int keyNo, int keyCol);
221  };
222 
223  template <typename FieldType>
226  for(int i = 0; i<GenericKey<FieldType>::size; i++){
227  key.m_values[i] = m_values[i];
228  }
229  return key;
230  }
231 
232  template <typename FieldType>
234  return getForeignKey(1);
235  }
236 
237  template <typename FieldType>
239  ASSERT_ALWAYS(keyNo<=1);
241  for(int i = 0; i<GenericKey<FieldType>::size; i++){
242  key.m_values[i] = m_values[getForeignKeyColNo(keyNo,i)];
243  }
244  return key;
245  }
246 
247  template <typename FieldType>
249  return getForeignKeyColNo(1, indexCol);
250  }
251 
252  template <typename FieldType>
253  int GenericRow<FieldType>::getForeignKeyColNo(int keyNo, int keyCol){
255  ASSERT_ALWAYS(keyCol<GenericKey<FieldType>::size);
256  return size-GenericKey<FieldType>::size-keyNo+keyCol;
257  }
258 
259  template <typename FieldType>
260  static bool operator==(const GenericRow<FieldType>& a, const GenericRow<FieldType>& b){
261  for(int i = 0; i<GenericRow<FieldType>::size; i++){
262  if(a.m_values[i].compare(b.m_values[i]) != 0){
263  return false;
264  }
265  }
266  return true;
267  }
268 
269  template <typename FieldType>
270  static bool operator==(const GenericKey<FieldType>& a, const GenericKey<FieldType>& b){
271  for(int i = 0; i<GenericKey<FieldType>::size; i++){
272  if(a.m_values[i].compare(b.m_values[i]) != 0){
273  return false;
274  }
275  }
276  return true;
277  }
278 
280  template <typename FieldType>
281  static bool lessOrEqual(const GenericRow<FieldType>& a, const GenericRow<FieldType>& b){
282  for(int i = 0; i<GenericKey<FieldType>::size; i++){
283  if(a.m_values[i].compare(b.m_values[i]) == 1){
284  return false;
285  }
286  }
287  return true;
288  }
289 
290  template <typename FieldType>
291  static NdbOut& operator<<(NdbOut& out, const GenericRow<FieldType>& row){
292  char buff[11];
293  out << "{";
294  for(int i = 0; i<GenericRow<FieldType>::size; i++){
295  out << row.m_values[i].toStr(buff);
297  out << ", ";
298  }
299  }
300  out << "}";
301  return out;
302  }
303 
304  //typedef GenericRow<IntField> Row;
305  //typedef GenericKey<IntField> Key;
306  typedef GenericRow<StrField> Row;
307  typedef GenericKey<StrField> Key;
308 
309 
310  static const char* colName(int colNo){
311  static const char* names[] = {
312  "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10"
313  };
314  ASSERT_ALWAYS(static_cast<unsigned int>(colNo)< sizeof names/sizeof names[0]);
315  return names[colNo];
316  };
317 
318  static void printMySQLError(MYSQL& mysql, const char* before=NULL){
319  if(before!=NULL){
320  ndbout << before;
321  }
322  ndbout << mysql_error(&mysql) << endl;
323  exit(-1);
324  }
325 
326  static void mySQLExec(MYSQL& mysql, const char* stmt){
327  ndbout << stmt << ";" << endl;
328  if(mysql_query(&mysql, stmt) != 0){
329  ndbout << "Error executing '" << stmt << "' : ";
330  printMySQLError(mysql);
331  }
332  }
333 
334  class Query;
335 
338  class Operation{
339  public:
340 
341  explicit Operation(class Query& query, Operation* parent);
342 
343  virtual ~Operation(){}
344 
345  //protected: FIXME
346  public:
347  friend class Query;
352  Vector<Operation*> m_children;
353  const NdbQueryOperationDef* m_operationDef;
354  // For now, only setResultRowRef() style result retrieval is tested.
355  union
356  {
357  const Row* m_resultPtr;
358  // Use union to avoid strict-aliasing problems.
359  const char* m_resultCharPtr;
360  };
361  // Corresponds to NdbQueryOperationDef operation numbering
362  Uint32 m_operationId;
363  // Number among siblings.
364  const Uint32 m_childNo;
365 
367  void verifyRow();
368 
370  virtual void verifyOwnRow() = 0;
371 
373  virtual void buildThis(NdbQueryBuilder& builder,
374  const NdbDictionary::Table& tab) = 0;
375 
377  void build(NdbQueryBuilder& builder,
378  const NdbDictionary::Table& tab);
380  virtual void submit()=0;
381 
382  void compareRows(const char* text,
383  const Row* expected,
384  const Row* actual) const;
385  };
386 
387  class Query{
388  public:
389 
390  explicit Query(Ndb& ndb);
391 
392  ~Query(){
393  m_builder->destroy();
394  if (m_queryDef != NULL)
395  {
396  m_queryDef->destroy();
397  }
398  }
399 
401  void build(const NdbDictionary::Table& tab, int tableSize);
402 
404  void submit(NdbTransaction& transaction);
405 
406  void submitOperation(Operation& operation) const;
407 
408  void setRoot(Operation& root){ m_root = &root;}
409 
410  NdbQuery::NextResultOutcome nextResult(){
411  return m_query->nextResult(true, false);
412  }
413 
415  void verifyRow() const {
416  m_root->verifyRow();
417  }
418 
419  Uint32 allocOperationId(){
420  return m_operationCount++;
421  }
422 
423  NdbQueryOperation* getOperation(Uint32 ident) const {
424  return m_query->getQueryOperation(ident);
425  }
426 
427  int getTableSize() const {
428  return m_tableSize;
429  }
430 
431  const NdbRecord* getNdbRecord() const {
432  return m_ndbRecord;
433  }
434 
435  const NdbDictionary::Dictionary* getDictionary() const {
436  return m_ndb.getDictionary();
437  }
438 
439  void close(bool forceSend = false){
440  m_query->close(forceSend);
441  }
442  private:
443  Ndb& m_ndb;
444  NdbQueryBuilder* const m_builder;
445  Operation* m_root;
446  const NdbQueryDef* m_queryDef;
447  NdbQuery* m_query;
448  Uint32 m_operationCount;
449  int m_tableSize;
450  const NdbRecord* m_ndbRecord;
451  };
452 
453 
454  class LookupOperation: public Operation{
455  public:
456  explicit LookupOperation(Query& query,
457  Operation* parent = NULL);
458  virtual void verifyOwnRow();
459 
461  virtual void submit();
462  protected:
463  virtual void buildThis(NdbQueryBuilder& builder,
464  const NdbDictionary::Table& tab);
465  };
466 
468  public:
469  explicit IndexLookupOperation(Query& query,
470  const char* indexName,
471  Operation* parent = NULL);
472  virtual void verifyOwnRow();
473 
475  virtual void submit();
476  protected:
477  virtual void buildThis(NdbQueryBuilder& builder,
478  const NdbDictionary::Table& tab);
479  private:
480  const char* const m_indexName;
481  };
482 
484  public:
485 
486  explicit TableScanOperation(Query& query, int lessThanRow=-1);
487 
488  virtual ~TableScanOperation() {
489  delete[] m_rowFound;
490  }
491 
492  virtual void verifyOwnRow();
493 
495  virtual void submit();
496 
497  protected:
498  virtual void buildThis(NdbQueryBuilder& builder,
499  const NdbDictionary::Table& tab);
500  private:
501  bool* m_rowFound;
502  int m_lessThanRow;
503  };
504 
506  public:
507 
508  explicit IndexScanOperation(Query& query,
509  const char* indexName,
510  int lowerBoundRowNo,
511  int upperBoundRowNo,
513 
514  virtual ~IndexScanOperation() {
515  delete[] m_rowFound;
516  }
517 
518  virtual void verifyOwnRow();
519 
521  virtual void submit();
522 
523  protected:
524  virtual void buildThis(NdbQueryBuilder& builder,
525  const NdbDictionary::Table& tab);
526  private:
527  const char* const m_indexName;
529  const int m_lowerBoundRowNo;
531  const int m_upperBoundRowNo;
533  bool* m_rowFound;
537  Row m_previousRow;
539  bool m_hasPreviousRow;
540  };
541 
542 
543  // Query methods.
544 
545  Query::Query(Ndb& ndb):
546  m_ndb(ndb),
547  m_builder(NdbQueryBuilder::create()),
548  m_root(NULL),
549  m_queryDef(NULL),
550  m_query(NULL),
551  m_operationCount(0),
552  m_tableSize(-1),
553  m_ndbRecord(NULL)
554  {
555  ASSERT_ALWAYS(m_builder != NULL);
556  }
557 
558  void Query::build(const NdbDictionary::Table& tab, int tableSize){
559  m_tableSize = tableSize;
560  m_root->build(*m_builder, tab);
561  m_queryDef = m_builder->prepare();
562  m_ndbRecord = tab.getDefaultRecord();
563  }
564 
565  void Query::submit(NdbTransaction& transaction){
566  m_query = transaction.createQuery(m_queryDef);
567  ASSERT_ALWAYS(m_query!=NULL);
568  submitOperation(*m_root);
569  }
570 
571  void Query::submitOperation(Operation& operation) const{
572  // Do a depth first traversal of the operations graph.
573  operation.submit();
574  for(Uint32 i = 0; i<operation.m_children.size(); i++){
575  submitOperation(*operation.m_children[i]);
576  }
577  }
578 
579  // Operation methods.
580  Operation::Operation(class Query& query,
581  Operation* parent):
582  m_query(query),
583  m_parent(parent),
584  m_operationDef(NULL),
585  m_resultPtr(NULL),
586  m_childNo(parent == NULL ? 0 : parent->m_children.size())
587  {
588  if(parent==NULL){
589  query.setRoot(*this);
590  }else{
591  parent->m_children.push_back(this);
592  }
593  }
594 
596  const NdbDictionary::Table& tab){
597  m_operationId = m_query.allocOperationId();
598  buildThis(builder, tab);
599  ASSERT_ALWAYS(builder.getNdbError().code==0);
600  for(Uint32 i = 0; i<m_children.size(); i++){
601  m_children[i]->build(builder, tab);
602  }
603  }
604 
606  verifyOwnRow();
607  for(Uint32 i = 0; i<m_children.size(); i++){
608  m_children[i]->verifyRow();
609  }
610  }
611 
612  typedef const char* constCharPtr;
613 
614  void Operation::compareRows(const char* text,
615  const Row* expected,
616  const Row* actual) const{
617  if(expected==NULL){
618  if(actual==NULL){
619  ndbout << text << " operationId=" << m_operationId
620  << " expected NULL and got it." << endl;
621  }else{
622  ndbout << text << " operationId=" << m_operationId
623  << " expected NULL but got." << *actual
624  << endl;
625  ASSERT_ALWAYS(false);
626  }
627  }else{
628  if(actual==NULL){
629  ndbout << text << " operationId=" << m_operationId
630  << " expected: " << *expected
631  << " but got NULL." << endl;
632  ASSERT_ALWAYS(false);
633  }else{
634  ndbout << text << " operationId=" << m_operationId
635  << " expected: " << *expected;
636  if(*expected == *actual){
637  ndbout << " and got it." << endl;
638  }else{
639  ndbout << " but got: " << *actual;
640  ASSERT_ALWAYS(false);
641  }
642  }
643  }
644  };
645 
646  // LookupOperation methods.
647 
648  LookupOperation
649  ::LookupOperation(Query& query,
650  Operation* parent):
651  Operation(query, parent){
652  }
653 
655  const NdbDictionary::Table& tab){
656  NdbQueryOperand* keyOperands[Key::size+2];
657  if(m_parent==NULL){
658  const Key key = Row(0).getPrimaryKey();
659  for(int i = 0; i<Key::size; i++){
660  keyOperands[i] = key.makeConstOperand(builder, i);
661  }
662  }else{
663  // Negative testing
664  ASSERT_ALWAYS(builder.linkedValue(m_parent->m_operationDef,
665  "unknown_col") == NULL);
666  ASSERT_ALWAYS(builder.getNdbError().code == QRY_UNKNOWN_COLUMN);
667 
668  for(int i = 0; i<Key::size; i++){
669  keyOperands[i] =
670  builder.linkedValue(m_parent->m_operationDef,
671  colName(Row::getForeignKeyColNo(
672  m_childNo, i)));
673  ASSERT_ALWAYS(keyOperands[i]!=NULL);
674  /*Row::makeLinkedKey(builder, keyOperands,
675  Operation::m_parent->m_operationDef,
676  Operation::m_childNo);*/
677  }
678  }
679  // Negative testing
680  keyOperands[Key::size] = keyOperands[0];
681  keyOperands[Key::size+1] = NULL;
682  ASSERT_ALWAYS(builder.readTuple(&tab, keyOperands)== NULL);
683  ASSERT_ALWAYS(builder.getNdbError().code == QRY_TOO_MANY_KEY_VALUES);
684  resetError(builder.getNdbError());
685 
686  keyOperands[Key::size] = NULL;
687  m_operationDef = builder.readTuple(&tab, keyOperands);
688  ASSERT_ALWAYS(m_operationDef != NULL);
689 
690  // Negative testing
691  keyOperands[Key::size-1] = builder.constValue(0x1fff1fff);
692  ASSERT_ALWAYS(keyOperands[Key::size-1] != NULL);
693  ASSERT_ALWAYS(builder.readTuple(&tab, keyOperands) == NULL);
694  ASSERT_ALWAYS(builder.getNdbError().code == QRY_OPERAND_HAS_WRONG_TYPE);
695 
696  // Negative testing
697  keyOperands[Key::size-1] = NULL;
698  ASSERT_ALWAYS(builder.readTuple(&tab, keyOperands) == NULL);
699  ASSERT_ALWAYS(builder.getNdbError().code == QRY_TOO_FEW_KEY_VALUES);
700  resetError(builder.getNdbError());
701  }
702 
704  NdbQueryOperation* queryOp
705  = m_query.getOperation(m_operationId);
706  // Negative testing
707  ASSERT_ALWAYS(queryOp->setResultRowRef(NULL,
708  m_resultCharPtr,
709  NULL) == -1);
710  ASSERT_ALWAYS(queryOp->getQuery().getNdbError().code ==
711  QRY_REQ_ARG_IS_NULL);
712  ASSERT_ALWAYS(queryOp->setOrdering(NdbQueryOptions::ScanOrdering_ascending)
713  == -1);
714  ASSERT_ALWAYS(queryOp->getQuery().getNdbError().code ==
715  QRY_WRONG_OPERATION_TYPE);
716 
717  ASSERT_ALWAYS(queryOp->setResultRowRef(m_query.getNdbRecord(),
718  m_resultCharPtr,
719  NULL) == 0);
720  // Negative testing
721  ASSERT_ALWAYS(queryOp->setResultRowRef(m_query.getNdbRecord(),
722  m_resultCharPtr,
723  NULL) == -1);
724  ASSERT_ALWAYS(queryOp->getQuery().getNdbError().code ==
725  QRY_RESULT_ROW_ALREADY_DEFINED);
726  }
727 
729  if(m_parent==NULL){
730  const Row expected(0);
731  compareRows("lookup root operation",
732  &expected,
733  m_resultPtr);
734  }else{
735  NdbQueryOperation* queryOp
736  = m_query
737  .getOperation(m_operationId);
738  if(!queryOp->getParentOperation(0)->isRowNULL()){
739  const Key key =
740  m_parent->m_resultPtr
741  ->getForeignKey(m_childNo);
742  bool found = false;
743  for(int i = 0; i<m_query.getTableSize(); i++){
744  const Row row(i);
745  if(row.getPrimaryKey() == key){
746  found = true;
747  compareRows("lookup child operation",
748  &row,
749  m_resultPtr);
750  }
751  }
752  if(!found && !queryOp->isRowNULL()){
753  compareRows("lookup child operation",
754  NULL,
755  m_resultPtr);
756  }
757  }
758  }
759  }
760 
761  // IndexLookupOperation methods.
762 
763  IndexLookupOperation
764  ::IndexLookupOperation(Query& query,
765  const char* indexName,
766  Operation* parent):
767  Operation(query, parent),
768  m_indexName(indexName){
769  }
770 
773  const NdbDictionary::Table& tab){
774  const NdbDictionary::Dictionary* const dict
775  = m_query.getDictionary();
776  char fullName[200];
777  sprintf(fullName, "%s$unique", m_indexName);
778  const NdbDictionary::Index* const index
779  = dict->getIndex(fullName, tab.getName());
780  ASSERT_ALWAYS(index!=NULL);
781 
782  NdbQueryOperand* keyOperands[Key::size+1];
783  if(m_parent==NULL){
784  const Key key = Row(0).getIndexKey();
785  for(int i = 0; i<Key::size; i++){
786  keyOperands[i] = key.makeConstOperand(builder, i);
787  }
788  }else{
789  for(int i = 0; i<Key::size; i++){
790  keyOperands[i] =
791  builder.linkedValue(m_parent->m_operationDef,
792  colName(Row::getForeignKeyColNo(
793  m_childNo, i)));
794  ASSERT_ALWAYS(keyOperands[i]!=NULL);
795  }
796  /*Row::makeLinkedKey(builder, keyOperands,
797  Operation::m_parent->m_operationDef,
798  Operation::m_childNo);*/
799  }
800  keyOperands[Key::size] = NULL;
801  m_operationDef = builder.readTuple(index, &tab, keyOperands);
802 
803  // Negative testing
804  const NdbDictionary::Index* const orderedIndex
805  = dict->getIndex(m_indexName, tab.getName());
806  ASSERT_ALWAYS(orderedIndex != NULL);
807  ASSERT_ALWAYS(builder.readTuple(orderedIndex, &tab, keyOperands) == NULL);
808  ASSERT_ALWAYS(builder.getNdbError().code == QRY_WRONG_INDEX_TYPE);
809  resetError(builder.getNdbError());
810  }
811 
813  NdbQueryOperation* queryOp
814  = m_query.getOperation(m_operationId);
815  queryOp->setResultRowRef(m_query.getNdbRecord(),
816  m_resultCharPtr,
817  NULL);
818  }
819 
821  if(m_parent==NULL){
822  const Row expected(0);
823  compareRows("index lookup root operation",
824  &expected,
825  m_resultPtr);
826  }else{
827  NdbQueryOperation* queryOp
828  = m_query
829  .getOperation(m_operationId);
830  if(!queryOp->getParentOperation(0)->isRowNULL()){
831  const Key key =
832  m_parent->m_resultPtr
833  ->getForeignKey(m_childNo);
834  bool found = false;
835  for(int i = 0; i<m_query.getTableSize(); i++){
836  const Row row(i);
837  if(row.getIndexKey() == key){
838  found = true;
839  compareRows("index lookup child operation",
840  &row,
841  m_resultPtr);
842  }
843  }
844  if(!found && !queryOp->isRowNULL()){
845  compareRows("index lookup child operation",
846  NULL,
847  m_resultPtr);
848  }
849  }
850  }
851  }
852 
853  // TableScanOperation methods.
854 
855  TableScanOperation
856  ::TableScanOperation(Query& query, int lessThanRow):
857  Operation(query, NULL),
858  m_rowFound(NULL),
859  m_lessThanRow(lessThanRow){
860  }
861 
863  const NdbDictionary::Table& tab){
864  m_operationDef = builder.scanTable(&tab);
865  m_rowFound = new bool[m_query.getTableSize()];
866  for(int i = 0; i<m_query.getTableSize(); i++){
867  m_rowFound[i] = false;
868  }
869  }
870 
872  NdbQueryOperation* queryOp
873  = m_query.getOperation(m_operationId);
874  queryOp->setResultRowRef(m_query.getNdbRecord(),
875  m_resultCharPtr,
876  NULL);
877  if(m_lessThanRow!=-1){
878  NdbInterpretedCode code(queryOp->getQueryOperationDef().getTable());
879  NdbScanFilter filter(&code);
880  ASSERT_ALWAYS(filter.begin()==0);
881  Row(m_lessThanRow).makeLessThanCond(filter);
882  ASSERT_ALWAYS(filter.end()==0);
883  ASSERT_ALWAYS(queryOp->setInterpretedCode(code)==0);
884  }
885  }
886 
888  bool found = false;
889  const int upperBound =
890  m_lessThanRow==-1 ?
891  m_query.getTableSize() :
892  m_lessThanRow;
893  for(int i = 0; i<upperBound; i++){
894  //const Row row(i);
895  if(Row(i) == *m_resultPtr){
896  found = true;
897  if(m_rowFound[i]){
898  ndbout << "Root table scan operation: "
899  << *m_resultPtr
900  << "appeared twice." << endl;
901  ASSERT_ALWAYS(false);
902  }
903  m_rowFound[i] = true;
904  }
905  }
906  if(!found){
907  ndbout << "Root table scan operation. Unexpected row: "
908  << *m_resultPtr << endl;
909  ASSERT_ALWAYS(false);
910  }else{
911  ndbout << "Root table scan operation. Got row: "
912  << *m_resultPtr
913  << " as expected." << endl;
914  }
915  }
916 
917  // IndexScanOperation methods.
918 
919  IndexScanOperation
920  ::IndexScanOperation(Query& query,
921  const char* indexName,
922  int lowerBoundRowNo,
923  int upperBoundRowNo,
925  Operation(query, NULL),
926  m_indexName(indexName),
927  m_lowerBoundRowNo(lowerBoundRowNo),
928  m_upperBoundRowNo(upperBoundRowNo),
929  m_ordering(ordering),
930  m_previousRow(0),
931  m_hasPreviousRow(false){
932  }
933 
935  const NdbDictionary::Table& tab){
936  const NdbDictionary::Dictionary* const dict
937  = m_query.getDictionary();
938  const NdbDictionary::Index* const index
939  = dict->getIndex(m_indexName, tab.getName());
940  ASSERT_ALWAYS(index!=NULL);
941 
942  const NdbQueryOperand* low[Key::size+1];
943  const NdbQueryOperand* high[Key::size+1];
944  // Code below assume that we use primary key index.
945  ASSERT_ALWAYS(strcmp(m_indexName, "PRIMARY")==0);
946  /* Tables are alway sorted on all columns. Using these bounds,
947  we therefore get m_upperBoundRowNo - m_lowerBoundRowNo +1 rows.*/
948  const Key& lowKey = *new Key(Row(m_lowerBoundRowNo).getPrimaryKey());
949  const Key& highKey = *new Key(Row(m_upperBoundRowNo).getPrimaryKey());
950 
951  for(int i = 0; i<Key::size; i++){
952  low[i] = lowKey.makeConstOperand(builder, i);
953  high[i] = highKey.makeConstOperand(builder, i);
954  }
955  low[Key::size] = NULL;
956  high[Key::size] = NULL;
957 
958  NdbQueryOptions options;
959  options.setOrdering(m_ordering);
960  const NdbQueryIndexBound bound(low, high);
961  const NdbQueryIndexScanOperationDef* opDef
962  = builder.scanIndex(index, &tab, &bound, &options);
963  m_operationDef = opDef;
964  ASSERT_ALWAYS(m_operationDef!=NULL);
965  m_rowFound = new bool[m_query.getTableSize()];
966  for(int i = 0; i<m_query.getTableSize(); i++){
967  m_rowFound[i] = false;
968  }
969  }
970 
972  NdbQueryOperation* queryOp
973  = m_query.getOperation(m_operationId);
974  queryOp->setResultRowRef(m_query.getNdbRecord(),
975  m_resultCharPtr,
976  NULL);
977 
978  // Negative testing.
979  if (m_ordering != NdbQueryOptions::ScanOrdering_unordered){
980  ASSERT_ALWAYS(queryOp
981  ->setOrdering(NdbQueryOptions::ScanOrdering_ascending) != 0);
982  ASSERT_ALWAYS(queryOp->getQuery().getNdbError().code ==
983  QRY_SCAN_ORDER_ALREADY_SET);
984 
985  ASSERT_ALWAYS(queryOp->setParallelism(1) != 0);
986  ASSERT_ALWAYS(queryOp->getQuery().getNdbError().code ==
987  QRY_SEQUENTIAL_SCAN_SORTED);
988  }
989  }
990 
992  bool found = false;
993  for(int i = m_lowerBoundRowNo; i<=m_upperBoundRowNo; i++){
994  const Row row(i);
995  if(row == *m_resultPtr){
996  found = true;
997  if(m_rowFound[i]){
998  ndbout << "Root index scan operation: "
999  << *m_resultPtr
1000  << "appeared twice." << endl;
1001  ASSERT_ALWAYS(false);
1002  }
1003  m_rowFound[i] = true;
1004  }
1005  }
1006  if(!found){
1007  ndbout << "Root index scan operation. Unexpected row: "
1008  << *m_resultPtr << endl;
1009  ASSERT_ALWAYS(false);
1010  }else{
1011  if(m_hasPreviousRow){
1012  switch(m_ordering){
1013  case NdbQueryOptions::ScanOrdering_ascending:
1014  if(!lessOrEqual(m_previousRow, *m_resultPtr)){
1015  ndbout << "Error in result ordering. Did not expect row "
1016  << *m_resultPtr
1017  << " now." << endl;
1018  ASSERT_ALWAYS(false);
1019  }
1020  break;
1021  case NdbQueryOptions::ScanOrdering_descending:
1022  if(lessOrEqual(m_previousRow, *m_resultPtr)){
1023  ndbout << "Error in result ordering. Did not expect row "
1024  << *m_resultPtr
1025  << " now." << endl;
1026  ASSERT_ALWAYS(false);
1027  }
1028  break;
1030  break;
1031  default:
1032  ASSERT_ALWAYS(false);
1033  }
1034  }
1035  m_hasPreviousRow = true;
1036  m_previousRow = *m_resultPtr;
1037  ndbout << "Root index scan operation. Got row: "
1038  << *m_resultPtr
1039  << " as expected." << endl;
1040  }
1041  }
1042 
1043  // Misc. functions.
1044 
1046  void makeTable(MYSQL& mysql, const char* name, int rowCount){
1047  char cmd[500];
1048  char piece[500];
1049  sprintf(cmd, "drop table if exists %s", name);
1050  mySQLExec(mysql, cmd);
1051  sprintf(cmd, "create table %s (\n", name);
1052  for(int i = 0; i<Row::size; i++){
1053  sprintf(piece, " %s %s NOT NULL,\n", colName(i), Row::getType(i));
1054  strcat(cmd, piece);
1055  }
1056  strcat(cmd, " PRIMARY KEY(");
1057  for(int i = 0; i<Key::size; i++){
1058  strcat(cmd, colName(i));
1059  if(i<Key::size - 1){
1060  strcat(cmd, ",");
1061  }else{
1062  strcat(cmd, "),\n");
1063  }
1064  }
1065  strcat(cmd, " UNIQUE KEY UIX (");
1066  for(int i = 0; i<Key::size; i++){
1067  strcat(cmd, colName(Row::getIndexKeyColNo(i)));
1068  if(i<Key::size - 1){
1069  strcat(cmd, ",");
1070  }else{
1071  strcat(cmd, "))\n");
1072  }
1073  }
1074  strcat(cmd, "ENGINE=NDB");
1075  mySQLExec(mysql, cmd);
1076  for(int i = 0; i<rowCount; i++){
1077  Row::makeSQLValues(piece, i);
1078  sprintf(cmd, "insert into %s %s", name, piece);
1079  mySQLExec(mysql, cmd);
1080  }
1081  };
1082 
1083 
1084  /* Execute a test for a give operation graph.*/
1085  void runCase(MYSQL& mysql,
1086  Ndb& ndb,
1087  Query& query,
1088  const char* tabName,
1089  int tabSize,
1090  int rowCount){
1091  // Populate test table.
1092  makeTable(mysql, tabName, tabSize);
1093  NdbDictionary::Dictionary* const dict = ndb.getDictionary();
1094  const NdbDictionary::Table* const tab = dict->getTable(tabName);
1095  ASSERT_ALWAYS(tab!=NULL);
1096  // Build generic query definition.
1097  query.build(*tab, tabSize);
1098  NdbTransaction* trans = ndb.startTransaction();
1099  ASSERT_ALWAYS(trans!=NULL);
1100  // instantiate query within transaction.
1101  query.submit(*trans);
1102  ASSERT_ALWAYS(trans->execute(NoCommit)==0);
1103  // Verify each row and total number of rows.
1104  for(int i = 0; i<rowCount; i++){
1105  ASSERT_ALWAYS(query.nextResult() == NdbQuery::NextResult_gotRow);
1106  query.verifyRow();
1107  if(false && i>3){
1108  // Enable to test close of incomplete scan.
1109  query.close();
1110  ndb.closeTransaction(trans);
1111  return;
1112  }
1113  }
1114  ASSERT_ALWAYS(query.nextResult() == NdbQuery::NextResult_scanComplete);
1115  ndb.closeTransaction(trans);
1116  }
1117 
1119  void runTestSuite(MYSQL& mysql, Ndb& ndb){
1120  for(int caseNo = 0; caseNo<7; caseNo++){
1121  ndbout << endl << "Running test case " << caseNo << endl;
1122 
1123  char tabName[20];
1124  sprintf(tabName, "t%d", caseNo);
1125  Query query(ndb);
1126 
1127  switch(caseNo){
1128  case 0:
1129  {
1130  LookupOperation root(query);
1131  LookupOperation child(query, &root);
1132  LookupOperation child2(query, &root);
1133  runCase(mysql, ndb, query, tabName, 1, 1);
1134  }
1135  break;
1136  case 1:
1137  {
1138  IndexLookupOperation root(query, "UIX");
1139  IndexLookupOperation child(query, "UIX", &root);
1140  runCase(mysql, ndb, query, tabName, 5, 1);
1141  }
1142  break;
1143  case 2:
1144  {
1145  IndexScanOperation root(query, "PRIMARY", 2, 4,
1147  LookupOperation child(query, &root);
1148  IndexLookupOperation child2(query, "UIX", &child);
1149  LookupOperation child3(query, &child);
1150  runCase(mysql, ndb, query, tabName, 5, 3);
1151  }
1152  break;
1153  case 3:
1154  {
1155  TableScanOperation root(query);
1156  LookupOperation child(query, &root);
1157  runCase(mysql, ndb, query, tabName, 5, 5);
1158  }
1159  break;
1160  case 4:
1161  {
1162  TableScanOperation root(query);
1163  IndexLookupOperation child1(query, "UIX", &root);
1164  LookupOperation child2(query, &child1);
1165  IndexLookupOperation child3(query, "UIX", &child2);
1166  LookupOperation child1_2(query, &root);
1167  LookupOperation child2_2(query, &child1_2);
1168  runCase(mysql, ndb, query, tabName, 10, 10);
1169  }
1170  break;
1171  case 5:
1172  {
1173  IndexScanOperation root(query, "PRIMARY", 0, 10,
1174  NdbQueryOptions::ScanOrdering_descending);
1175  LookupOperation child(query, &root);
1176  runCase(mysql, ndb, query, tabName, 10, 10);
1177  }
1178  break;
1179  case 6:
1180  {
1181  TableScanOperation root(query, 3);
1182  LookupOperation child(query, &root);
1183  runCase(mysql, ndb, query, tabName, 5, 3);
1184  }
1185  break;
1186 #if 0
1187  default:
1188  //case 6:
1189  {
1190  IndexScanOperation root(query, "PRIMARY", 0, 1000,
1191  NdbQueryOptions::ScanOrdering_descending);
1192  LookupOperation child(query, &root);
1193  runCase(mysql, ndb, query, tabName, 10*(caseNo-6), 10*(caseNo-6));
1194  }
1195  break;
1196 #endif
1197  }
1198  }
1199  }
1200 
1201 };
1202 
1203 
1204 using namespace SPJSanityTest;
1205 
1206 int main(int argc, char* argv[]){
1207  if(argc!=4){
1208  ndbout << "Usage: " << argv[0]
1209  << " <mysql IP address> <mysql port> <cluster connect string>"
1210  << endl;
1211  return NDBT_ProgramExit(NDBT_FAILED);
1212  }
1213  const char* const host=argv[1];
1214  const int port = atoi(argv[2]);
1215  const char* const connectString = argv[3];
1216 
1217  NDB_INIT(argv[0]);
1218  MYSQL mysql;
1219  ASSERT_ALWAYS(mysql_init(&mysql));
1220  if(!mysql_real_connect(&mysql, host, "root", "", "",
1221  port, NULL, 0)){
1222  printMySQLError(mysql, "mysql_real_connect() failed:");
1223  return NDBT_ProgramExit(NDBT_FAILED);
1224  }
1225  mySQLExec(mysql, "create database if not exists CK_DB");
1226  mySQLExec(mysql, "use CK_DB");
1227  {
1228  Ndb_cluster_connection con(connectString);
1229  if(con.connect(12, 5, 1) != 0){
1230  ndbout << "Unable to connect to management server." << endl;
1231  return NDBT_ProgramExit(NDBT_FAILED);
1232  }
1233 
1234  int res = con.wait_until_ready(30,30);
1235  if (res != 0){
1236  ndbout << "Cluster nodes not ready in 30 seconds." << endl;
1237  return NDBT_ProgramExit(NDBT_FAILED);
1238  }
1239  Ndb ndb(&con, "CK_DB");
1240  if(ndb.init() != 0){
1241  ERR(ndb.getNdbError());
1242  return NDBT_ProgramExit(NDBT_FAILED);
1243  }
1244  runTestSuite(mysql, ndb);
1245  } // Must call ~Ndb_cluster_connection() before ndb_end().
1246  ndb_end(0);
1247  return 0;
1248 }
1249 
1250 // Explicit template instantiations.
1251 template class Vector<Operation*>;
1252 template class GenericRow<IntField>;
1253 template class GenericKey<IntField>;
1254 template class GenericRow<StrField>;
1255 template class GenericKey<StrField>;