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