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 <assert.h>
26 #include <mysql.h>
27 #include <mysqld_error.h>
28 
29 //#include <iostream>
30 //#include <stdio.h>
31 
32 #include <ndb_global.h>
33 #include <ndb_opts.h>
34 #include <NDBT.hpp>
35 #include <NdbApi.hpp>
36 #include "../../src/ndbapi/NdbQueryBuilder.hpp"
37 #include "../../src/ndbapi/NdbQueryOperation.hpp"
38 #include <pthread.h>
39 #include <NdbTick.h>
40 
41 
42 
43 #ifdef NDEBUG
44 // Some asserts have side effects, and there is no other error handling anyway.
45 #define ASSERT_ALWAYS(cond) if(unlikely(!(cond))){abort();}
46 #else
47 #define ASSERT_ALWAYS assert
48 #endif
49 
50 #if 0
51 /**
52  * Helper debugging macros
53  */
54 #define PRINT_ERROR(code,msg) \
55   std::cout << "Error in " << __FILE__ << ", line: " << __LINE__ \
56             << ", code: " << code \
57             << ", msg: " << msg << "." << std::endl
58 
59 #define APIERROR(error) { \
60   PRINT_ERROR((error).code,(error).message); \
61   exit(-1); }
62 #endif
63 
64 
65 //const char* databaseName = "TEST_DB";
66 //const char* tableName = "T";
67 const char* databaseName = "PTDB";
68 const char* tableName = "TT";
69 
70 class TestParameters{
71 public:
72   int m_iterations;
73   /** Number of child lookup operations.*/
74   int m_depth;
75   int m_scanLength; // m_scanLength==0 means root should be lookup.
76   /** Specifies how many times a query definition should be reused before.
77    * It is recreated. Setting this to 0 means that the definition is never
78    * recreated.*/
79   int m_queryDefReuse;
80   bool m_useLinkedOperations;
81   /** If true, run an equivalent SQL query.*/
82   bool m_useSQL;
83 
TestParameters()84   explicit TestParameters(){bzero(this, sizeof *this);}
85 };
86 
87 /** Entry point for new posix threads.*/
88 static void *callback(void* thread);
89 
90 class TestThread{
91   friend void *callback(void* thread);
92 public:
93   explicit TestThread(Ndb_cluster_connection& con, const char* host, int port);
94   ~TestThread();
95   /** Initiate a new test.*/
96   void start(const TestParameters& params);
97   /** Wait fo current test to complete.*/
98   void wait();
99 private:
100   struct Row{
101     Uint32 a;
102     Uint32 b;
103   };
104 
105   struct KeyRow{
106     Uint32 a;
107   };
108 
109   const TestParameters* m_params;
110   Ndb m_ndb;
111   enum {State_Active, State_Stopping, State_Stopped} m_state;
112   pthread_t m_posixThread;
113   pthread_mutex_t m_mutex;
114   pthread_cond_t m_condition;
115   const NdbDictionary::Table* m_tab;
116   const NdbDictionary::Index* m_index;
117   const NdbRecord* m_resultRec;
118   const NdbRecord* m_keyRec;
119   const NdbRecord* m_indexRec;
120   MYSQL m_mysql;
121 
122   /** Entry point for POSIX thread.*/
123   void run();
124   void doLinkedAPITest();
125   void doNonLinkedAPITest();
126   void doSQLTest();
127 };
128 
callback(void * thread)129 static void *callback(void* thread){
130   reinterpret_cast<TestThread*>(thread)->run();
131   return NULL;
132 }
133 
printMySQLError(MYSQL & mysql,const char * before=NULL)134 static void printMySQLError(MYSQL& mysql, const char* before=NULL){
135   if(before!=NULL){
136     ndbout << before;
137   }
138   ndbout << mysql_error(&mysql) << endl;
139   exit(-1);
140 }
141 
mySQLExec(MYSQL & mysql,const char * stmt)142 static void mySQLExec(MYSQL& mysql, const char* stmt){
143   //ndbout << stmt << endl;
144   if(mysql_query(&mysql, stmt) != 0){
145     ndbout << "Error executing '" << stmt << "' : ";
146     printMySQLError(mysql);
147   }
148   mysql_free_result(mysql_use_result(&mysql));
149 }
150 
151 // TestThread methods.
TestThread(Ndb_cluster_connection & con,const char * host,int port)152 TestThread::TestThread(Ndb_cluster_connection& con,
153                        const char* host,
154                        int port):
155   m_params(NULL),
156   m_ndb(&con, databaseName),
157   m_state(State_Active){
158   ASSERT_ALWAYS(m_ndb.init()==0);
159   ASSERT_ALWAYS(pthread_mutex_init(&m_mutex, NULL)==0);
160   ASSERT_ALWAYS(pthread_cond_init(&m_condition, NULL)==0);
161   ASSERT_ALWAYS(pthread_create(&m_posixThread, NULL, callback, this)
162                 ==0);
163   NdbDictionary::Dictionary*  const dict = m_ndb.getDictionary();
164   m_tab = dict->getTable(tableName);
165 
166   m_index = dict->getIndex("PRIMARY", tableName);
167   ASSERT_ALWAYS(m_index != NULL);
168 
169   /* Create NdbRecord for row. */
170   m_resultRec = m_tab->getDefaultRecord();
171   ASSERT_ALWAYS(m_resultRec!=NULL);
172 
173   /* Create NdbRecord for primary key. */
174   const NdbDictionary::Column *col1= m_tab->getColumn("a");
175   ASSERT_ALWAYS(col1 != NULL);
176   NdbDictionary::RecordSpecification spec = {
177     col1, 0, 0, 0
178   };
179 
180   m_keyRec = dict->createRecord(m_tab, &spec, 1, sizeof spec);
181   ASSERT_ALWAYS(m_keyRec != NULL);
182 
183   m_indexRec = m_index->getDefaultRecord();
184   ASSERT_ALWAYS(m_indexRec != NULL);
185 
186   // Make SQL connection.
187   ASSERT_ALWAYS(mysql_init(&m_mysql));
188   if(!mysql_real_connect(&m_mysql, host, "root", "", "",
189                          port, NULL, 0)){
190     printMySQLError(m_mysql, "mysql_real_connect() failed:");
191     ASSERT_ALWAYS(false);
192   }
193   char text[50];
194   sprintf(text, "use %s", databaseName);
195   mySQLExec(m_mysql, text);
196 }
197 
~TestThread()198 TestThread::~TestThread(){
199   ASSERT_ALWAYS(pthread_mutex_lock(&m_mutex)==0);
200   // Tell thread to stop.
201   m_state = State_Stopping;
202   ASSERT_ALWAYS(pthread_cond_signal(&m_condition)==0);
203   // Wait for thread to stop.
204   while(m_state != State_Stopped){
205     ASSERT_ALWAYS(pthread_cond_wait(&m_condition, &m_mutex)==0);
206   }
207   ASSERT_ALWAYS(m_params == NULL);
208   ASSERT_ALWAYS(pthread_mutex_unlock(&m_mutex)==0);
209 
210   ASSERT_ALWAYS(pthread_cond_destroy(&m_condition)==0);
211   ASSERT_ALWAYS(pthread_mutex_destroy(&m_mutex)==0);
212 }
213 
start(const TestParameters & params)214 void TestThread::start(const TestParameters& params){
215   ASSERT_ALWAYS(pthread_mutex_lock(&m_mutex)==0);
216   ASSERT_ALWAYS(m_params == NULL);
217   m_params = &params;
218   ASSERT_ALWAYS(pthread_cond_signal(&m_condition)==0);
219   ASSERT_ALWAYS(pthread_mutex_unlock(&m_mutex)==0);
220 }
221 
run()222 void TestThread::run(){
223 
224   ASSERT_ALWAYS(pthread_mutex_lock(&m_mutex)==0);
225   while(true){
226     while(m_params==NULL && m_state==State_Active){
227       // Wait for a new command from master thread.
228       ASSERT_ALWAYS(pthread_cond_wait(&m_condition, &m_mutex)==0);
229     }
230     if(m_state != State_Active){
231       // We have been told to stop.
232       ASSERT_ALWAYS(m_state == State_Stopping);
233       m_state = State_Stopped;
234       // Wake up master thread and release lock.
235       ASSERT_ALWAYS(pthread_cond_signal(&m_condition)==0);
236       ASSERT_ALWAYS(pthread_mutex_unlock(&m_mutex)==0);
237       // Exit thread.
238       return;
239     }
240 
241     if(m_params->m_useSQL){
242       doSQLTest();
243     }else{
244       if(m_params->m_useLinkedOperations){
245         doLinkedAPITest();
246       }else{
247         doNonLinkedAPITest();
248       }
249     }
250 
251     ASSERT_ALWAYS(m_params != NULL);
252     m_params = NULL;
253     ASSERT_ALWAYS(pthread_cond_signal(&m_condition)==0);
254   }
255 }
256 
doLinkedAPITest()257 void TestThread::doLinkedAPITest(){
258   NdbQueryBuilder* const builder = NdbQueryBuilder::create();
259 
260   const NdbQueryDef* queryDef = NULL;
261   const Row** resultPtrs = new const Row*[m_params->m_depth+1];
262 
263   NdbTransaction* trans = NULL;
264 
265   for(int iterNo = 0; iterNo<m_params->m_iterations; iterNo++){
266     //ndbout << "Starting next iteration " << endl;
267     // Build query definition if needed.
268     if(iterNo==0 || (m_params->m_queryDefReuse>0 &&
269                      iterNo%m_params->m_queryDefReuse==0)){
270       if(queryDef != NULL){
271         queryDef->destroy();
272       }
273       const NdbQueryOperationDef* parentOpDef = NULL;
274       if(m_params->m_scanLength==0){
275         // Root is lookup
276         const NdbQueryOperand* rootKey[] = {
277           builder->constValue(0), //a
278           NULL
279         };
280         parentOpDef = builder->readTuple(m_tab, rootKey);
281       }else if(m_params->m_scanLength==1){ //Pruned scan
282         const NdbQueryOperand* const key[] = {
283           builder->constValue(m_params->m_scanLength),
284           NULL
285         };
286 
287         const NdbQueryIndexBound eqBound(key);
288         parentOpDef = builder->scanIndex(m_index, m_tab, &eqBound);
289       }else{
290         // Root is index scan with single bound.
291         const NdbQueryOperand* const highKey[] = {
292           builder->constValue(m_params->m_scanLength),
293           NULL
294         };
295 
296         const NdbQueryIndexBound bound(NULL, false, highKey, false);
297         parentOpDef = builder->scanIndex(m_index, m_tab, &bound);
298       }
299 
300       // Add child lookup operations.
301       for(int i = 0; i<m_params->m_depth; i++){
302         const NdbQueryOperand* key[] = {
303           builder->linkedValue(parentOpDef, "b"),
304           NULL
305         };
306         parentOpDef = builder->readTuple(m_tab, key);
307       }
308       queryDef = builder->prepare();
309     }
310 
311     if (!trans) {
312       trans = m_ndb.startTransaction();
313     }
314     // Execute query.
315     NdbQuery* const query = trans->createQuery(queryDef);
316     for(int i = 0; i<m_params->m_depth+1; i++){
317       query->getQueryOperation(i)
318         ->setResultRowRef(m_resultRec,
319                           reinterpret_cast<const char*&>(resultPtrs[i]),
320                           NULL);
321     }
322     int res = trans->execute(NoCommit);
323     //        if (res != 0)
324     //          APIERROR(trans->getNdbError());
325     ASSERT_ALWAYS(res == 0);
326     int cnt=0;
327     while(true){
328       const NdbQuery::NextResultOutcome outcome
329         = query->nextResult(true, false);
330       if(outcome ==  NdbQuery::NextResult_scanComplete){
331         break;
332       }
333       ASSERT_ALWAYS(outcome== NdbQuery::NextResult_gotRow);
334       cnt++;
335       //        if (m_params->m_scanLength==0)
336       //          break;
337     }
338     ASSERT_ALWAYS(cnt== MAX(1,m_params->m_scanLength));
339     //      query->close();
340     if ((iterNo % 5) == 0) {
341       m_ndb.closeTransaction(trans);
342       trans = NULL;
343     }
344   }
345   if (trans) {
346     m_ndb.closeTransaction(trans);
347     trans = NULL;
348   }
349   builder->destroy();
350 }
351 
doNonLinkedAPITest()352 void TestThread::doNonLinkedAPITest(){
353   Row row = {0, 0};
354   NdbTransaction* const trans = m_ndb.startTransaction();
355   for(int iterNo = 0; iterNo<m_params->m_iterations; iterNo++){
356     //      NdbTransaction* const trans = m_ndb.startTransaction();
357     if(m_params->m_scanLength>0){
358       const KeyRow highKey = { m_params->m_scanLength };
359       NdbIndexScanOperation* scanOp = NULL;
360       if(m_params->m_scanLength==1){ // Pruned scan
361         const NdbIndexScanOperation::IndexBound bound = {
362           reinterpret_cast<const char*>(&highKey),
363           1, // Low key count.
364           true, // Low key inclusive
365           reinterpret_cast<const char*>(&highKey),
366           1, // High key count.
367           true, // High key inclusive.
368           0
369         };
370 
371         scanOp =
372           trans->scanIndex(m_indexRec,
373                            m_resultRec,
374                            NdbOperation::LM_Dirty,
375                            NULL, // Result mask
376                            &bound);
377       }else{
378         // Scan with upper bound only.
379         const NdbIndexScanOperation::IndexBound bound = {
380           NULL, // Low key
381           0, // Low key count.
382           false, // Low key inclusive
383           reinterpret_cast<const char*>(&highKey),
384           1, // High key count.
385           false, // High key inclusive.
386           0
387         };
388 
389         scanOp =
390           trans->scanIndex(m_indexRec,
391                            m_resultRec,
392                            NdbOperation::LM_Dirty,
393                            NULL, // Result mask
394                            &bound);
395       }
396       ASSERT_ALWAYS(scanOp != NULL);
397 
398       ASSERT_ALWAYS(trans->execute(NoCommit) == 0);
399 
400       // Iterate over scan result
401       int cnt = 0;
402       while(true){
403         const Row* scanRow = NULL;
404         const int retVal =
405           scanOp->nextResult(reinterpret_cast<const char**>(&scanRow),
406                              true,
407                              false);
408         if(retVal==1){
409           break;
410         }
411         ASSERT_ALWAYS(retVal== 0);
412         //ndbout << "ScanRow: " << scanRow->a << " " << scanRow->b << endl;
413         row = *scanRow;
414 
415         // Do a chain of lookups for each scan row.
416         for(int i = 0; i < m_params->m_depth; i++){
417           const KeyRow key = {row.b};
418           const NdbOperation* const lookupOp =
419             trans->readTuple(m_keyRec,
420                              reinterpret_cast<const char*>(&key),
421                              m_resultRec,
422                              reinterpret_cast<char*>(&row),
423                              NdbOperation::LM_Dirty);
424           ASSERT_ALWAYS(lookupOp != NULL);
425           ASSERT_ALWAYS(trans->execute(NoCommit) == 0);
426           //ndbout << "LookupRow: " << row.a << " " << row.b << endl;
427         }
428         cnt++;
429         //          if (m_params->m_scanLength==0)
430         //            break;
431       }
432       ASSERT_ALWAYS(cnt== m_params->m_scanLength);
433       scanOp->close(false,true);
434     }else{
435       // Root is lookup.
436       for(int i = 0; i < m_params->m_depth+1; i++){
437         const KeyRow key = {row.b};
438         const NdbOperation* const lookupOp =
439           trans->readTuple(m_keyRec,
440                            reinterpret_cast<const char*>(&key),
441                            m_resultRec,
442                            reinterpret_cast<char*>(&row),
443                            NdbOperation::LM_Dirty);
444         ASSERT_ALWAYS(lookupOp != NULL);
445         ASSERT_ALWAYS(trans->execute(NoCommit) == 0);
446       }
447     }//if(m_params->m_isScan)
448     //      m_ndb.closeTransaction(trans);
449   }//for(int iterNo = 0; iterNo<m_params->m_iterations; iterNo++)
450   m_ndb.closeTransaction(trans);
451 }
452 
453 static bool printQuery = false;
454 
doSQLTest()455 void TestThread::doSQLTest(){
456   if(m_params->m_useLinkedOperations){
457     mySQLExec(m_mysql, "set ndb_join_pushdown = on;");
458   }else{
459     mySQLExec(m_mysql, "set ndb_join_pushdown = off;");
460   }
461   mySQLExec(m_mysql, "SET SESSION query_cache_type = OFF");
462 
463   class TextBuf{
464   public:
465     char m_buffer[1000];
466 
467     explicit TextBuf(){m_buffer[0] = '\0';}
468 
469     // For appending to the string.
470     char* tail(){ return m_buffer + strlen(m_buffer);}
471   };
472 
473   TextBuf text;
474 
475   sprintf(text.tail(), "select * from ");
476   for(int i = 0; i<m_params->m_depth+1; i++){
477     sprintf(text.tail(), "%s t%d", tableName, i);
478     if(i < m_params->m_depth){
479       sprintf(text.tail(), ", ");
480     }else{
481       sprintf(text.tail(), " where ");
482     }
483   }
484 
485   if(m_params->m_scanLength==0){
486     // Root is lookup
487     sprintf(text.tail(), "t0.a=0 ");
488   }else{
489     // Root is scan.
490     sprintf(text.tail(), "t0.a<%d ", m_params->m_scanLength);
491   }
492 
493   for(int i = 1; i<m_params->m_depth+1; i++){
494     // Compare primary key of Tn to attribute of Tn-1.
495     sprintf(text.tail(), "and t%d.b=t%d.a ", i-1, i);
496   }
497   if(printQuery){
498     ndbout << text.m_buffer << endl;
499   }
500 
501   for(int i = 0; i < m_params->m_iterations; i++){
502     mySQLExec(m_mysql, text.m_buffer);
503   }
504 }
505 
wait()506 void TestThread::wait(){
507   ASSERT_ALWAYS(pthread_mutex_lock(&m_mutex)==0);
508   while(m_params!=NULL){
509     ASSERT_ALWAYS(pthread_cond_wait(&m_condition, &m_mutex)==0);
510   }
511   ASSERT_ALWAYS(pthread_mutex_unlock(&m_mutex)==0);
512 }
513 
514 
515 
516 
makeDatabase(const char * host,int port,int rowCount)517 static void makeDatabase(const char* host, int port, int rowCount){
518   MYSQL mysql;
519   ASSERT_ALWAYS(mysql_init(&mysql));
520   if(!mysql_real_connect(&mysql, host, "root", "", "",
521                          port, NULL, 0)){
522     printMySQLError(mysql, "mysql_real_connect() failed:");
523     ASSERT_ALWAYS(false);
524   }
525   char text[200];
526   sprintf(text, "create database if not exists %s", databaseName);
527   mySQLExec(mysql, text);
528   sprintf(text, "use %s", databaseName);
529   mySQLExec(mysql, text);
530   sprintf(text, "drop table if exists %s", tableName);
531   mySQLExec(mysql, text);
532   sprintf(text, "create table %s(a int not null,"
533           "b int not null,"
534           "primary key(a)) ENGINE=NDB", tableName);
535   mySQLExec(mysql, text);
536   for(int i = 0; i<rowCount; i++){
537     sprintf(text, "insert into %s values(%d, %d)", tableName,
538             i, (i+1)%rowCount);
539     mySQLExec(mysql, text);
540   }
541 }
542 
printHeading()543 static void printHeading(){
544   ndbout << endl << "Use SQL; Use linked; Thread count; Iterations; "
545     "Scan length; Depth; Def re-use; Duration (ms); Tuples per sec;" << endl;
546 }
547 
548 
runTest(TestThread ** threads,int threadCount,TestParameters & param)549 void runTest(TestThread** threads, int threadCount,
550              TestParameters& param){
551   //ndbout << "Doing test " << name << endl;
552   const NDB_TICKS start = NdbTick_CurrentMillisecond();
553   for(int i = 0; i<threadCount; i++){
554     threads[i]->start(param);
555   }
556   for(int i = 0; i<threadCount; i++){
557     threads[i]->wait();
558   }
559   const NDB_TICKS duration = NdbTick_CurrentMillisecond() - start;
560   ndbout << param.m_useSQL << "; ";
561   ndbout << param.m_useLinkedOperations << "; ";
562   ndbout << threadCount << "; ";
563   ndbout << param.m_iterations << "; ";
564   ndbout << param.m_scanLength << "; ";
565   ndbout << param.m_depth <<"; ";
566   ndbout << param.m_queryDefReuse << "; ";
567   ndbout << duration << "; ";
568   int tupPerSec;
569   if(duration==0){
570     tupPerSec = -1;
571   }else{
572     if(param.m_scanLength==0){
573       tupPerSec = threadCount *
574         param.m_iterations *
575         (param.m_depth+1) * 1000 / duration;
576     }else{
577       tupPerSec = threadCount *
578         param.m_iterations *
579         param.m_scanLength *
580         (param.m_depth+1) * 1000 / duration;
581     }
582   }
583   ndbout << tupPerSec << "; ";
584   ndbout << endl;
585   //ndbout << "Test " << name << " done in " << duration << "ms"<< endl;
586 }
587 
588 const int threadCount = 1;
589 TestThread** threads = NULL;
590 
warmUp()591 void warmUp(){
592   ndbout << endl << "warmUp()" << endl;
593   TestParameters param;
594   param.m_useSQL = true;
595   param.m_iterations = 10;
596   param.m_useLinkedOperations = false;
597   param.m_scanLength = 0;
598   param.m_queryDefReuse = 0;
599 
600   printHeading();
601   for(int i = 0; i<20; i++){
602     param.m_depth = i;
603     runTest(threads, threadCount, param);
604   }
605   printHeading();
606   param.m_useLinkedOperations = true;
607   for(int i = 0; i<20; i++){
608     param.m_depth = i;
609     runTest(threads, threadCount, param);
610   }
611 }
612 
testLookupDepth(bool useSQL)613 void testLookupDepth(bool useSQL){
614   ndbout << endl << "testLookupDepth()" << endl;
615   TestParameters param;
616   param.m_useSQL = useSQL;
617   param.m_iterations = 100;
618   param.m_useLinkedOperations = false;
619   param.m_scanLength = 0;
620   param.m_queryDefReuse = 0;
621 
622   printHeading();
623   for(int i = 0; i<20; i++){
624     param.m_depth = i;
625     runTest(threads, threadCount, param);
626   }
627   printHeading();
628   param.m_useLinkedOperations = true;
629   for(int i = 0; i<20; i++){
630     param.m_depth = i;
631     runTest(threads, threadCount, param);
632   }
633 }
634 
testScanDepth(int scanLength,bool useSQL)635 void testScanDepth(int scanLength, bool useSQL){
636   ndbout  << endl << "testScanDepth()" << endl;
637   TestParameters param;
638   param.m_useSQL = useSQL;
639   param.m_iterations = 20;
640   param.m_useLinkedOperations = false;
641   param.m_scanLength = scanLength;
642   param.m_queryDefReuse = 0;
643   printHeading();
644   for(int i = 0; i<10; i++){
645     param.m_depth = i;
646     runTest(threads, threadCount, param);
647   }
648   printHeading();
649   param.m_useLinkedOperations = true;
650   for(int i = 0; i<10; i++){
651     param.m_depth = i;
652     runTest(threads, threadCount, param);
653   }
654 }
655 
main(int argc,char * argv[])656 int main(int argc, char* argv[]){
657   NDB_INIT(argv[0]);
658   if(argc!=4 && argc!=5){
659     ndbout << "Usage: " << argv[0] << " [--print-query]"
660            << " <mysql IP address> <mysql port> <cluster connect string>"
661            << endl;
662     return -1;
663   }
664   int argno = 1;
665   if(strcmp(argv[argno],"--print-query")==0){
666     printQuery = true;
667     argno++;
668   }
669   const char* const host=argv[argno++];
670   const int port = atoi(argv[argno++]);
671   const char* const connectString = argv[argno];
672 
673   makeDatabase(host, port, 200);
674   {
675     Ndb_cluster_connection con(connectString);
676     ASSERT_ALWAYS(con.connect(12, 5, 1) == 0);
677     ASSERT_ALWAYS(con.wait_until_ready(30,30) == 0);
678 
679     const int threadCount = 1;
680     threads = new TestThread*[threadCount];
681     for(int i = 0; i<threadCount; i++){
682       threads[i] = new TestThread(con, host, port);
683     }
684     sleep(1);
685 
686     //testScanDepth(1);
687     //testScanDepth(2);
688     //testScanDepth(5);
689     warmUp();
690     testScanDepth(50, true);
691     testLookupDepth(true);
692 
693     for(int i = 0; i<threadCount; i++){
694       delete threads[i];
695     }
696     delete[] threads;
697   } // Must call ~Ndb_cluster_connection() before ndb_end().
698   ndb_end(0);
699   return 0;
700 }
701 
702