1 /*
2 Copyright (c) 2011, 2021, Oracle and/or its affiliates.
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 <mysql.h>
27 #include <mysqld_error.h>
28
29 #include <ndb_global.h>
30 #include <ndb_opts.h>
31 #include <NDBT.hpp>
32 #include <NdbApi.hpp>
33 #include "../../src/ndbapi/NdbQueryBuilder.hpp"
34 #include "../../src/ndbapi/NdbQueryOperation.hpp"
35
36
37 /* TODO:
38 - RecAttr and setResultRowBuff result retrieval.
39 - Parameter operands.
40 - Add another table type (e.g. with CHAR() fields.)
41 */
42
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_SORTED 4824
67 #define QRY_BATCH_SIZE_TOO_SMALL 4825
68
69
70 namespace SPJSanityTest{
71
resetError(const NdbError & err)72 static void resetError(const NdbError& err)
73 {
74 new (&const_cast<NdbError&>(err)) NdbError;
75 }
76
77 class IntField{
78 public:
getType()79 static const char* getType(){
80 return "INT";
81 }
82
IntField(int i=0)83 IntField(int i=0):
84 m_val(i)
85 {}
86
toStr(char * buff) const87 const char* toStr(char* buff) const {
88 sprintf(buff, "%d", m_val);
89 return buff;
90 }
91
compare(const IntField & other) const92 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
getValue() const101 Uint64 getValue() const{
102 return m_val;
103 }
104
getSize() const105 Uint32 getSize() const{
106 return sizeof m_val;
107 }
108
109 private:
110 uint m_val;
111 };
112
113 class StrField{
114 public:
getType()115 static const char* getType(){
116 return "VARCHAR(10)";
117 }
118
StrField(int i=0)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
toStr(char * buff) const125 const char* toStr(char* buff) const {
126 sprintf(buff, "'%s'", getValue());
127 return buff;
128 }
129
compare(const StrField & other) const130 int compare(const StrField& other) const{
131 return strcmp(getValue(), other.getValue());
132 }
133
getValue() const134 const char* getValue() const{
135 m_val[m_len] = '\0';
136 return m_val;
137 }
138
getSize() const139 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
makeConstOperand(NdbQueryBuilder & builder,int fieldNo) const157 NdbConstOperand* makeConstOperand(NdbQueryBuilder& builder,
158 int fieldNo) const {
159 require(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
getType(int colNo)182 static const char *getType(int colNo){
183 //return "INT";
184 return FieldType::getType();
185 }
186
makeSQLValues(char * buffer,int rowNo)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
makeLessThanCond(NdbScanFilter & scanFilter)209 void makeLessThanCond(NdbScanFilter& scanFilter){
210 //require(scanFilter.lt(0, m_values[0].getValue())==0);
211 require(scanFilter.cmp(NdbScanFilter::COND_LT, 0, m_values, m_values[0].getSize())==0);
212 }
213
214 /** Get the row column number that corresponds to the n'th column
215 * of the index.*/
216 static int getIndexKeyColNo(int indexCol);
217
218 /** Get the row column number that corresponds to the n'th column
219 * of the m'th foreign key..*/
220 static int getForeignKeyColNo(int keyNo, int keyCol);
221 };
222
223 template <typename FieldType>
getPrimaryKey() const224 GenericKey<FieldType> GenericRow<FieldType>::getPrimaryKey() const {
225 GenericKey<FieldType> key;
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>
getIndexKey() const233 GenericKey<FieldType> GenericRow<FieldType>::getIndexKey() const {
234 return getForeignKey(1);
235 }
236
237 template <typename FieldType>
getForeignKey(int keyNo) const238 GenericKey<FieldType> GenericRow<FieldType>::getForeignKey(int keyNo) const {
239 require(keyNo<=1);
240 GenericKey<FieldType> key;
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>
getIndexKeyColNo(int indexCol)248 int GenericRow<FieldType>::getIndexKeyColNo(int indexCol){
249 return getForeignKeyColNo(1, indexCol);
250 }
251
252 template <typename FieldType>
getForeignKeyColNo(int keyNo,int keyCol)253 int GenericRow<FieldType>::getForeignKeyColNo(int keyNo, int keyCol){
254 require(keyNo<GenericRow<FieldType>::size-GenericKey<FieldType>::size);
255 require(keyCol<GenericKey<FieldType>::size);
256 return size-GenericKey<FieldType>::size-keyNo+keyCol;
257 }
258
259 template <typename FieldType>
operator ==(const GenericRow<FieldType> & a,const GenericRow<FieldType> & b)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>
operator ==(const GenericKey<FieldType> & a,const GenericKey<FieldType> & b)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
279 /** Returns true if key of a <= key of b.*/
280 template <typename FieldType>
lessOrEqual(const GenericRow<FieldType> & a,const GenericRow<FieldType> & b)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>
operator <<(NdbOut & out,const GenericRow<FieldType> & row)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);
296 if(i<GenericRow<FieldType>::size-1){
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
colName(int colNo)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 require(static_cast<unsigned int>(colNo)< sizeof names/sizeof names[0]);
315 return names[colNo];
316 };
317
printMySQLError(MYSQL & mysql,const char * before=NULL)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
mySQLExec(MYSQL & mysql,const char * stmt)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
336 /** Class representing a single NdbQueryOperation. 'Row'
337 * is a template argument, to allow different table defintions.*/
338 class Operation{
339 public:
340
341 explicit Operation(class Query& query, Operation* parent);
342
~Operation()343 virtual ~Operation(){}
344
345 //protected: FIXME
346 public:
347 friend class Query;
348 /** Enclosing NdbQuery.*/
349 Query& m_query;
350 /** Optional parent operation.*/
351 const Operation* m_parent;
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
366 /** Check that result of this op and descendans is ok.*/
367 void verifyRow();
368
369 /** Check that result of this op is ok.*/
370 virtual void verifyOwnRow() = 0;
371
372 /** Build operation definition.*/
373 virtual void buildThis(NdbQueryBuilder& builder,
374 const NdbDictionary::Table& tab) = 0;
375
376 /** Build this and descendants.*/
377 void build(NdbQueryBuilder& builder,
378 const NdbDictionary::Table& tab);
379 /** Set up result retrieval before execution.*/
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
~Query()392 ~Query(){
393 m_builder->destroy();
394 if (m_queryDef != NULL)
395 {
396 m_queryDef->destroy();
397 }
398 }
399
400 /** Build query definition.*/
401 void build(const NdbDictionary::Table& tab, int tableSize);
402
403 /** Execute within transaction.*/
404 void submit(NdbTransaction& transaction);
405
406 void submitOperation(Operation& operation) const;
407
setRoot(Operation & root)408 void setRoot(Operation& root){ m_root = &root;}
409
nextResult()410 NdbQuery::NextResultOutcome nextResult(){
411 return m_query->nextResult(true, false);
412 }
413
414 /** Verify current row for all operations.*/
verifyRow() const415 void verifyRow() const {
416 m_root->verifyRow();
417 }
418
allocOperationId()419 Uint32 allocOperationId(){
420 return m_operationCount++;
421 }
422
getOperation(Uint32 ident) const423 NdbQueryOperation* getOperation(Uint32 ident) const {
424 return m_query->getQueryOperation(ident);
425 }
426
getTableSize() const427 int getTableSize() const {
428 return m_tableSize;
429 }
430
getNdbRecord() const431 const NdbRecord* getNdbRecord() const {
432 return m_ndbRecord;
433 }
434
getDictionary() const435 const NdbDictionary::Dictionary* getDictionary() const {
436 return m_ndb.getDictionary();
437 }
438
close(bool forceSend=false)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
460 /** Set up result retrieval before execution.*/
461 virtual void submit();
462 protected:
463 virtual void buildThis(NdbQueryBuilder& builder,
464 const NdbDictionary::Table& tab);
465 };
466
467 class IndexLookupOperation: public Operation{
468 public:
469 explicit IndexLookupOperation(Query& query,
470 const char* indexName,
471 Operation* parent = NULL);
472 virtual void verifyOwnRow();
473
474 /** Set up result retrieval before execution.*/
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
483 class TableScanOperation: public Operation{
484 public:
485
486 explicit TableScanOperation(Query& query, int lessThanRow=-1);
487
~TableScanOperation()488 virtual ~TableScanOperation() {
489 delete[] m_rowFound;
490 }
491
492 virtual void verifyOwnRow();
493
494 /** Set up result retrieval before execution.*/
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
505 class IndexScanOperation: public Operation{
506 public:
507
508 explicit IndexScanOperation(Query& query,
509 const char* indexName,
510 int lowerBoundRowNo,
511 int upperBoundRowNo,
512 NdbQueryOptions::ScanOrdering ordering);
513
~IndexScanOperation()514 virtual ~IndexScanOperation() {
515 delete[] m_rowFound;
516 }
517
518 virtual void verifyOwnRow();
519
520 /** Set up result retrieval before execution.*/
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;
528 /** Number of table row from which get key to use as lower bound.*/
529 const int m_lowerBoundRowNo;
530 /** Number of table row from which get key to use as upper bound.*/
531 const int m_upperBoundRowNo;
532 /** An entry per row. True if row has been seen in the result stream.*/
533 bool* m_rowFound;
534 /** Ordering of results.*/
535 NdbQueryOptions::ScanOrdering m_ordering;
536 /** Previous row, for verifying ordering.*/
537 Row m_previousRow;
538 /** True from the second row and onwards.*/
539 bool m_hasPreviousRow;
540 };
541
542
543 // Query methods.
544
Query(Ndb & ndb)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 require(m_builder != NULL);
556 }
557
build(const NdbDictionary::Table & tab,int tableSize)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
submit(NdbTransaction & transaction)565 void Query::submit(NdbTransaction& transaction){
566 m_query = transaction.createQuery(m_queryDef);
567 require(m_query!=NULL);
568 submitOperation(*m_root);
569 }
570
submitOperation(Operation & operation) const571 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.
Operation(class Query & query,Operation * parent)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
build(NdbQueryBuilder & builder,const NdbDictionary::Table & tab)595 void Operation::build(NdbQueryBuilder& builder,
596 const NdbDictionary::Table& tab){
597 m_operationId = m_query.allocOperationId();
598 buildThis(builder, tab);
599 require(builder.getNdbError().code==0);
600 for(Uint32 i = 0; i<m_children.size(); i++){
601 m_children[i]->build(builder, tab);
602 }
603 }
604
verifyRow()605 void Operation::verifyRow(){
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
compareRows(const char * text,const Row * expected,const Row * actual) const614 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 require(false);
626 }
627 }else{
628 if(actual==NULL){
629 ndbout << text << " operationId=" << m_operationId
630 << " expected: " << *expected
631 << " but got NULL." << endl;
632 require(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 require(false);
641 }
642 }
643 }
644 };
645
646 // LookupOperation methods.
647
648 LookupOperation
LookupOperation(Query & query,Operation * parent)649 ::LookupOperation(Query& query,
650 Operation* parent):
651 Operation(query, parent){
652 }
653
buildThis(NdbQueryBuilder & builder,const NdbDictionary::Table & tab)654 void LookupOperation::buildThis(NdbQueryBuilder& builder,
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 require(builder.linkedValue(m_parent->m_operationDef,
665 "unknown_col") == NULL);
666 require(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 require(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 require(builder.readTuple(&tab, keyOperands)== NULL);
683 require(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 require(m_operationDef != NULL);
689
690 // Negative testing
691 keyOperands[Key::size-1] = builder.constValue(0x1fff1fff);
692 require(keyOperands[Key::size-1] != NULL);
693 require(builder.readTuple(&tab, keyOperands) == NULL);
694 require(builder.getNdbError().code == QRY_OPERAND_HAS_WRONG_TYPE);
695
696 // Negative testing
697 keyOperands[Key::size-1] = NULL;
698 require(builder.readTuple(&tab, keyOperands) == NULL);
699 require(builder.getNdbError().code == QRY_TOO_FEW_KEY_VALUES);
700 resetError(builder.getNdbError());
701 }
702
submit()703 void LookupOperation::submit(){
704 NdbQueryOperation* queryOp
705 = m_query.getOperation(m_operationId);
706 // Negative testing
707 require(queryOp->setResultRowRef(NULL,
708 m_resultCharPtr,
709 NULL) == -1);
710 require(queryOp->getQuery().getNdbError().code ==
711 QRY_REQ_ARG_IS_NULL);
712 require(
713 queryOp->setOrdering(NdbQueryOptions::ScanOrdering_ascending) == -1);
714 require(
715 queryOp->getQuery().getNdbError().code == QRY_WRONG_OPERATION_TYPE);
716
717 require(queryOp->setResultRowRef(m_query.getNdbRecord(),
718 m_resultCharPtr,
719 NULL) == 0);
720 // Negative testing
721 require(queryOp->setResultRowRef(m_query.getNdbRecord(),
722 m_resultCharPtr,
723 NULL) == -1);
724 require(queryOp->getQuery().getNdbError().code ==
725 QRY_RESULT_ROW_ALREADY_DEFINED);
726 }
727
verifyOwnRow()728 void LookupOperation::verifyOwnRow(){
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
IndexLookupOperation(Query & query,const char * indexName,Operation * parent)764 ::IndexLookupOperation(Query& query,
765 const char* indexName,
766 Operation* parent):
767 Operation(query, parent),
768 m_indexName(indexName){
769 }
770
771 void IndexLookupOperation
buildThis(NdbQueryBuilder & builder,const NdbDictionary::Table & tab)772 ::buildThis(NdbQueryBuilder& builder,
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 require(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 require(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 require(orderedIndex != NULL);
807 require(builder.readTuple(orderedIndex, &tab, keyOperands) == NULL);
808 require(builder.getNdbError().code == QRY_WRONG_INDEX_TYPE);
809 resetError(builder.getNdbError());
810 }
811
submit()812 void IndexLookupOperation::submit(){
813 NdbQueryOperation* queryOp
814 = m_query.getOperation(m_operationId);
815 queryOp->setResultRowRef(m_query.getNdbRecord(),
816 m_resultCharPtr,
817 NULL);
818 }
819
verifyOwnRow()820 void IndexLookupOperation::verifyOwnRow(){
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
TableScanOperation(Query & query,int lessThanRow)856 ::TableScanOperation(Query& query, int lessThanRow):
857 Operation(query, NULL),
858 m_rowFound(NULL),
859 m_lessThanRow(lessThanRow){
860 }
861
buildThis(NdbQueryBuilder & builder,const NdbDictionary::Table & tab)862 void TableScanOperation::buildThis(NdbQueryBuilder& builder,
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
submit()871 void TableScanOperation::submit(){
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 require(filter.begin()==0);
881 Row(m_lessThanRow).makeLessThanCond(filter);
882 require(filter.end()==0);
883 require(queryOp->setInterpretedCode(code)==0);
884 }
885 }
886
verifyOwnRow()887 void TableScanOperation::verifyOwnRow(){
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 require(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 require(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
IndexScanOperation(Query & query,const char * indexName,int lowerBoundRowNo,int upperBoundRowNo,NdbQueryOptions::ScanOrdering ordering)920 ::IndexScanOperation(Query& query,
921 const char* indexName,
922 int lowerBoundRowNo,
923 int upperBoundRowNo,
924 NdbQueryOptions::ScanOrdering ordering):
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
buildThis(NdbQueryBuilder & builder,const NdbDictionary::Table & tab)934 void IndexScanOperation::buildThis(NdbQueryBuilder& builder,
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 require(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 require(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 require(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
submit()971 void IndexScanOperation::submit(){
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 require(
981 queryOp->setOrdering(NdbQueryOptions::ScanOrdering_ascending) != 0);
982 require(
983 queryOp->getQuery().getNdbError().code == QRY_SCAN_ORDER_ALREADY_SET);
984
985 require(queryOp->setParallelism(1) != 0);
986 require(
987 queryOp->getQuery().getNdbError().code == QRY_SEQUENTIAL_SCAN_SORTED);
988 }
989 }
990
verifyOwnRow()991 void IndexScanOperation::verifyOwnRow(){
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 require(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 require(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 require(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 require(false);
1027 }
1028 break;
1029 case NdbQueryOptions::ScanOrdering_unordered:
1030 break;
1031 default:
1032 require(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
1045 /** Make and populate SQL table.*/
makeTable(MYSQL & mysql,const char * name,int rowCount)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.*/
runCase(MYSQL & mysql,Ndb & ndb,Query & query,const char * tabName,int tabSize,int rowCount)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 require(tab!=NULL);
1096 // Build generic query definition.
1097 query.build(*tab, tabSize);
1098 NdbTransaction* trans = ndb.startTransaction();
1099 require(trans!=NULL);
1100 // instantiate query within transaction.
1101 query.submit(*trans);
1102 require(trans->execute(NoCommit)==0);
1103 // Verify each row and total number of rows.
1104 for(int i = 0; i<rowCount; i++){
1105 require(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 require(query.nextResult() == NdbQuery::NextResult_scanComplete);
1115 ndb.closeTransaction(trans);
1116 }
1117
1118 /** Run a set of test cases.*/
runTestSuite(MYSQL & mysql,Ndb & ndb)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,
1146 NdbQueryOptions::ScanOrdering_unordered);
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
main(int argc,char * argv[])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 require(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>;
1256