21 #include <mysqld_error.h>
23 #include <ndb_global.h>
27 #include "../../src/ndbapi/NdbQueryBuilder.hpp"
28 #include "../../src/ndbapi/NdbQueryOperation.hpp"
39 #define ASSERT_ALWAYS(cond) if(!(cond)){abort();}
41 #define ASSERT_ALWAYS assert
44 #define QRY_REQ_ARG_IS_NULL 4800
45 #define QRY_TOO_FEW_KEY_VALUES 4801
46 #define QRY_TOO_MANY_KEY_VALUES 4802
47 #define QRY_OPERAND_HAS_WRONG_TYPE 4803
48 #define QRY_CHAR_OPERAND_TRUNCATED 4804
49 #define QRY_NUM_OPERAND_RANGE 4805
50 #define QRY_MULTIPLE_PARENTS 4806
51 #define QRY_UNKONWN_PARENT 4807
52 #define QRY_UNKNOWN_COLUMN 4808
53 #define QRY_UNRELATED_INDEX 4809
54 #define QRY_WRONG_INDEX_TYPE 4810
55 #define QRY_OPERAND_ALREADY_BOUND 4811
56 #define QRY_DEFINITION_TOO_LARGE 4812
57 #define QRY_SEQUENTIAL_SCAN_SORTED 4813
58 #define QRY_RESULT_ROW_ALREADY_DEFINED 4814
59 #define QRY_HAS_ZERO_OPERATIONS 4815
60 #define QRY_IN_ERROR_STATE 4816
61 #define QRY_ILLEGAL_STATE 4817
62 #define QRY_WRONG_OPERATION_TYPE 4820
63 #define QRY_SCAN_ORDER_ALREADY_SET 4821
64 #define QRY_PARAMETER_HAS_WRONG_TYPE 4822
65 #define QRY_CHAR_PARAMETER_TRUNCATED 4823
66 #define QRY_MULTIPLE_SCAN_BRANCHES 4824
67 #define QRY_MULTIPLE_SCAN_SORTED 4825
70 namespace SPJSanityTest{
72 static void resetError(
const NdbError& err)
74 new (&
const_cast<NdbError&
>(err)) NdbError;
79 static const char* getType(){
87 const char* toStr(
char* buff)
const {
88 sprintf(buff,
"%d", m_val);
92 int compare(
const IntField& other)
const{
93 if (m_val > other.m_val)
95 else if (m_val == other.m_val)
101 Uint64 getValue()
const{
105 Uint32 getSize()
const{
115 static const char* getType(){
116 return "VARCHAR(10)";
122 sprintf(m_val,
"c%5d",
i);
125 const char* toStr(
char* buff)
const {
126 sprintf(buff,
"'%s'", getValue());
130 int compare(
const StrField& other)
const{
131 return strcmp(getValue(), other.getValue());
134 const char* getValue()
const{
139 Uint32 getSize()
const{
141 return strlen(m_val);
146 mutable char m_val[10];
151 template <
typename FieldType>
154 static const int size = 2;
155 FieldType m_values[size];
159 ASSERT_ALWAYS(fieldNo<size);
161 return builder.constValue(m_values[fieldNo].getValue());
166 template <
typename FieldType>
169 static const int size = 4;
171 FieldType m_values[size];
177 for(
int i = 0;
i<size;
i++){
178 m_values[
i] = FieldType(
i+rowNo);
182 static const char *getType(
int colNo){
184 return FieldType::getType();
187 static void makeSQLValues(
char* buffer,
int rowNo){
189 sprintf(buffer,
"values(");
190 char* tail = buffer+strlen(buffer);
191 for(
int i = 0;
i<size;
i++){
195 sprintf(tail,
"%s,", row.m_values[
i].toStr(tmp));
197 sprintf(tail,
"%s)", row.m_values[
i].toStr(tmp));
199 tail = buffer+strlen(buffer);
209 void makeLessThanCond(NdbScanFilter& scanFilter){
223 template <
typename FieldType>
227 key.m_values[
i] = m_values[
i];
232 template <
typename FieldType>
234 return getForeignKey(1);
237 template <
typename FieldType>
239 ASSERT_ALWAYS(keyNo<=1);
242 key.m_values[
i] = m_values[getForeignKeyColNo(keyNo,
i)];
247 template <
typename FieldType>
249 return getForeignKeyColNo(1, indexCol);
252 template <
typename FieldType>
259 template <
typename FieldType>
262 if(a.m_values[
i].compare(b.m_values[
i]) != 0){
269 template <
typename FieldType>
272 if(a.m_values[
i].compare(b.m_values[
i]) != 0){
280 template <
typename FieldType>
283 if(a.m_values[
i].compare(b.m_values[
i]) == 1){
290 template <
typename FieldType>
291 static NdbOut& operator<<(NdbOut& out, const GenericRow<FieldType>& row){
295 out << row.m_values[
i].toStr(buff);
310 static const char* colName(
int colNo){
311 static const char* names[] = {
312 "c1",
"c2",
"c3",
"c4",
"c5",
"c6",
"c7",
"c8",
"c9",
"c10"
314 ASSERT_ALWAYS(static_cast<unsigned int>(colNo)<
sizeof names/
sizeof names[0]);
318 static void printMySQLError(
MYSQL& mysql,
const char* before=NULL){
322 ndbout << mysql_error(&mysql) << endl;
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);
357 const Row* m_resultPtr;
359 const char* m_resultCharPtr;
362 Uint32 m_operationId;
364 const Uint32 m_childNo;
382 void compareRows(
const char* text,
384 const Row* actual)
const;
390 explicit Query(Ndb& ndb);
394 if (m_queryDef != NULL)
396 m_queryDef->destroy();
404 void submit(NdbTransaction& transaction);
406 void submitOperation(
Operation& operation)
const;
408 void setRoot(
Operation& root){ m_root = &root;}
419 Uint32 allocOperationId(){
420 return m_operationCount++;
424 return m_query->getQueryOperation(ident);
427 int getTableSize()
const {
436 return m_ndb.getDictionary();
439 void close(
bool forceSend =
false){
440 m_query->
close(forceSend);
448 Uint32 m_operationCount;
470 const char* indexName,
480 const char*
const m_indexName;
509 const char* indexName,
527 const char*
const m_indexName;
529 const int m_lowerBoundRowNo;
531 const int m_upperBoundRowNo;
539 bool m_hasPreviousRow;
545 Query::Query(Ndb& ndb):
555 ASSERT_ALWAYS(m_builder != NULL);
559 m_tableSize = tableSize;
560 m_root->
build(*m_builder, tab);
561 m_queryDef = m_builder->prepare();
566 m_query = transaction.createQuery(m_queryDef);
567 ASSERT_ALWAYS(m_query!=NULL);
568 submitOperation(*m_root);
571 void Query::submitOperation(
Operation& operation)
const{
574 for(Uint32
i = 0;
i<operation.m_children.size();
i++){
575 submitOperation(*operation.m_children[
i]);
584 m_operationDef(NULL),
586 m_childNo(parent == NULL ? 0 : parent->m_children.
size())
589 query.setRoot(*
this);
591 parent->m_children.push_back(
this);
597 m_operationId =
m_query.allocOperationId();
600 for(Uint32
i = 0;
i<m_children.size();
i++){
601 m_children[
i]->build(builder, tab);
607 for(Uint32
i = 0;
i<m_children.size();
i++){
608 m_children[
i]->verifyRow();
612 typedef const char* constCharPtr;
614 void Operation::compareRows(
const char* text,
616 const Row* actual)
const{
619 ndbout << text <<
" operationId=" << m_operationId
620 <<
" expected NULL and got it." << endl;
622 ndbout << text <<
" operationId=" << m_operationId
623 <<
" expected NULL but got." << *actual
625 ASSERT_ALWAYS(
false);
629 ndbout << text <<
" operationId=" << m_operationId
630 <<
" expected: " << *expected
631 <<
" but got NULL." << endl;
632 ASSERT_ALWAYS(
false);
634 ndbout << text <<
" operationId=" << m_operationId
635 <<
" expected: " << *expected;
636 if(*expected == *actual){
637 ndbout <<
" and got it." << endl;
639 ndbout <<
" but got: " << *actual;
640 ASSERT_ALWAYS(
false);
649 ::LookupOperation(
Query& query,
658 const Key key =
Row(0).getPrimaryKey();
660 keyOperands[
i] = key.makeConstOperand(builder,
i);
664 ASSERT_ALWAYS(builder.linkedValue(
m_parent->m_operationDef,
665 "unknown_col") == NULL);
666 ASSERT_ALWAYS(builder.
getNdbError().code == QRY_UNKNOWN_COLUMN);
670 builder.linkedValue(
m_parent->m_operationDef,
673 ASSERT_ALWAYS(keyOperands[
i]!=NULL);
681 keyOperands[Key::size+1] = NULL;
682 ASSERT_ALWAYS(builder.readTuple(&tab, keyOperands)== NULL);
683 ASSERT_ALWAYS(builder.
getNdbError().code == QRY_TOO_MANY_KEY_VALUES);
687 m_operationDef = builder.readTuple(&tab, keyOperands);
688 ASSERT_ALWAYS(m_operationDef != NULL);
691 keyOperands[Key::size-1] = builder.constValue(0x1fff1fff);
692 ASSERT_ALWAYS(keyOperands[Key::size-1] != NULL);
693 ASSERT_ALWAYS(builder.readTuple(&tab, keyOperands) == NULL);
694 ASSERT_ALWAYS(builder.
getNdbError().code == QRY_OPERAND_HAS_WRONG_TYPE);
697 keyOperands[Key::size-1] = NULL;
698 ASSERT_ALWAYS(builder.readTuple(&tab, keyOperands) == NULL);
699 ASSERT_ALWAYS(builder.
getNdbError().code == QRY_TOO_FEW_KEY_VALUES);
705 =
m_query.getOperation(m_operationId);
707 ASSERT_ALWAYS(queryOp->setResultRowRef(NULL,
710 ASSERT_ALWAYS(queryOp->getQuery().
getNdbError().code ==
711 QRY_REQ_ARG_IS_NULL);
712 ASSERT_ALWAYS(queryOp->
setOrdering(NdbQueryOptions::ScanOrdering_ascending)
714 ASSERT_ALWAYS(queryOp->getQuery().
getNdbError().code ==
715 QRY_WRONG_OPERATION_TYPE);
717 ASSERT_ALWAYS(queryOp->setResultRowRef(
m_query.getNdbRecord(),
721 ASSERT_ALWAYS(queryOp->setResultRowRef(
m_query.getNdbRecord(),
724 ASSERT_ALWAYS(queryOp->getQuery().
getNdbError().code ==
725 QRY_RESULT_ROW_ALREADY_DEFINED);
730 const Row expected(0);
731 compareRows(
"lookup root operation",
737 .getOperation(m_operationId);
738 if(!queryOp->getParentOperation(0)->isRowNULL()){
741 ->getForeignKey(m_childNo);
743 for(
int i = 0;
i<
m_query.getTableSize();
i++){
745 if(row.getPrimaryKey() == key){
747 compareRows(
"lookup child operation",
752 if(!found && !queryOp->isRowNULL()){
753 compareRows(
"lookup child operation",
764 ::IndexLookupOperation(
Query& query,
765 const char* indexName,
768 m_indexName(indexName){
775 = m_query.getDictionary();
777 sprintf(fullName,
"%s$unique", m_indexName);
780 ASSERT_ALWAYS(index!=NULL);
784 const Key key =
Row(0).getIndexKey();
785 for(
int i = 0;
i<Key::size;
i++){
786 keyOperands[
i] = key.makeConstOperand(builder,
i);
789 for(
int i = 0;
i<Key::size;
i++){
791 builder.linkedValue(m_parent->m_operationDef,
794 ASSERT_ALWAYS(keyOperands[
i]!=NULL);
800 keyOperands[Key::size] = NULL;
801 m_operationDef = builder.readTuple(index, &tab, keyOperands);
806 ASSERT_ALWAYS(orderedIndex != NULL);
807 ASSERT_ALWAYS(builder.readTuple(orderedIndex, &tab, keyOperands) == NULL);
808 ASSERT_ALWAYS(builder.
getNdbError().code == QRY_WRONG_INDEX_TYPE);
814 =
m_query.getOperation(m_operationId);
815 queryOp->setResultRowRef(
m_query.getNdbRecord(),
822 const Row expected(0);
823 compareRows(
"index lookup root operation",
829 .getOperation(m_operationId);
830 if(!queryOp->getParentOperation(0)->isRowNULL()){
833 ->getForeignKey(m_childNo);
835 for(
int i = 0;
i<
m_query.getTableSize();
i++){
837 if(row.getIndexKey() == key){
839 compareRows(
"index lookup child operation",
844 if(!found && !queryOp->isRowNULL()){
845 compareRows(
"index lookup child operation",
856 ::TableScanOperation(
Query& query,
int lessThanRow):
859 m_lessThanRow(lessThanRow){
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;
873 =
m_query.getOperation(m_operationId);
874 queryOp->setResultRowRef(
m_query.getNdbRecord(),
877 if(m_lessThanRow!=-1){
879 NdbScanFilter filter(&
code);
880 ASSERT_ALWAYS(filter.begin()==0);
881 Row(m_lessThanRow).makeLessThanCond(filter);
882 ASSERT_ALWAYS(filter.end()==0);
889 const int upperBound =
893 for(
int i = 0;
i<upperBound;
i++){
895 if(
Row(
i) == *m_resultPtr){
898 ndbout <<
"Root table scan operation: "
900 <<
"appeared twice." << endl;
901 ASSERT_ALWAYS(
false);
903 m_rowFound[
i] =
true;
907 ndbout <<
"Root table scan operation. Unexpected row: "
908 << *m_resultPtr << endl;
909 ASSERT_ALWAYS(
false);
911 ndbout <<
"Root table scan operation. Got row: "
913 <<
" as expected." << endl;
920 ::IndexScanOperation(
Query& query,
921 const char* indexName,
926 m_indexName(indexName),
927 m_lowerBoundRowNo(lowerBoundRowNo),
928 m_upperBoundRowNo(upperBoundRowNo),
929 m_ordering(ordering),
931 m_hasPreviousRow(false){
940 ASSERT_ALWAYS(index!=NULL);
945 ASSERT_ALWAYS(strcmp(m_indexName,
"PRIMARY")==0);
948 const Key& lowKey = *
new Key(
Row(m_lowerBoundRowNo).getPrimaryKey());
949 const Key& highKey = *
new Key(
Row(m_upperBoundRowNo).getPrimaryKey());
952 low[
i] = lowKey.makeConstOperand(builder,
i);
953 high[
i] = highKey.makeConstOperand(builder,
i);
962 = builder.scanIndex(index, &tab, &bound, &options);
963 m_operationDef = opDef;
964 ASSERT_ALWAYS(m_operationDef!=NULL);
965 m_rowFound =
new bool[
m_query.getTableSize()];
966 for(
int i = 0;
i<
m_query.getTableSize();
i++){
967 m_rowFound[
i] =
false;
973 =
m_query.getOperation(m_operationId);
974 queryOp->setResultRowRef(
m_query.getNdbRecord(),
980 ASSERT_ALWAYS(queryOp
981 ->setOrdering(NdbQueryOptions::ScanOrdering_ascending) != 0);
982 ASSERT_ALWAYS(queryOp->getQuery().
getNdbError().code ==
983 QRY_SCAN_ORDER_ALREADY_SET);
986 ASSERT_ALWAYS(queryOp->getQuery().
getNdbError().code ==
987 QRY_SEQUENTIAL_SCAN_SORTED);
993 for(
int i = m_lowerBoundRowNo;
i<=m_upperBoundRowNo;
i++){
995 if(row == *m_resultPtr){
998 ndbout <<
"Root index scan operation: "
1000 <<
"appeared twice." << endl;
1001 ASSERT_ALWAYS(
false);
1003 m_rowFound[
i] =
true;
1007 ndbout <<
"Root index scan operation. Unexpected row: "
1008 << *m_resultPtr << endl;
1009 ASSERT_ALWAYS(
false);
1011 if(m_hasPreviousRow){
1013 case NdbQueryOptions::ScanOrdering_ascending:
1014 if(!lessOrEqual(m_previousRow, *m_resultPtr)){
1015 ndbout <<
"Error in result ordering. Did not expect row "
1018 ASSERT_ALWAYS(
false);
1021 case NdbQueryOptions::ScanOrdering_descending:
1022 if(lessOrEqual(m_previousRow, *m_resultPtr)){
1023 ndbout <<
"Error in result ordering. Did not expect row "
1026 ASSERT_ALWAYS(
false);
1032 ASSERT_ALWAYS(
false);
1035 m_hasPreviousRow =
true;
1036 m_previousRow = *m_resultPtr;
1037 ndbout <<
"Root index scan operation. Got row: "
1039 <<
" as expected." << endl;
1046 void makeTable(
MYSQL& mysql,
const char*
name,
int rowCount){
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));
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){
1062 strcat(cmd,
"),\n");
1065 strcat(cmd,
" UNIQUE KEY UIX (");
1066 for(
int i = 0;
i<Key::size;
i++){
1068 if(
i<Key::size - 1){
1071 strcat(cmd,
"))\n");
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);
1085 void runCase(
MYSQL& mysql,
1088 const char* tabName,
1092 makeTable(mysql, tabName, tabSize);
1095 ASSERT_ALWAYS(tab!=NULL);
1097 query.
build(*tab, tabSize);
1098 NdbTransaction* trans = ndb.startTransaction();
1099 ASSERT_ALWAYS(trans!=NULL);
1102 ASSERT_ALWAYS(trans->execute(NoCommit)==0);
1104 for(
int i = 0;
i<rowCount;
i++){
1105 ASSERT_ALWAYS(query.nextResult() == NdbQuery::NextResult_gotRow);
1110 ndb.closeTransaction(trans);
1114 ASSERT_ALWAYS(query.nextResult() == NdbQuery::NextResult_scanComplete);
1115 ndb.closeTransaction(trans);
1119 void runTestSuite(
MYSQL& mysql, Ndb& ndb){
1120 for(
int caseNo = 0; caseNo<7; caseNo++){
1121 ndbout << endl <<
"Running test case " << caseNo << endl;
1124 sprintf(tabName,
"t%d", caseNo);
1133 runCase(mysql, ndb, query, tabName, 1, 1);
1140 runCase(mysql, ndb, query, tabName, 5, 1);
1150 runCase(mysql, ndb, query, tabName, 5, 3);
1157 runCase(mysql, ndb, query, tabName, 5, 5);
1168 runCase(mysql, ndb, query, tabName, 10, 10);
1174 NdbQueryOptions::ScanOrdering_descending);
1176 runCase(mysql, ndb, query, tabName, 10, 10);
1183 runCase(mysql, ndb, query, tabName, 5, 3);
1191 NdbQueryOptions::ScanOrdering_descending);
1193 runCase(mysql, ndb, query, tabName, 10*(caseNo-6), 10*(caseNo-6));
1204 using namespace SPJSanityTest;
1206 int main(
int argc,
char* argv[]){
1208 ndbout <<
"Usage: " << argv[0]
1209 <<
" <mysql IP address> <mysql port> <cluster connect string>"
1211 return NDBT_ProgramExit(NDBT_FAILED);
1213 const char*
const host=argv[1];
1214 const int port = atoi(argv[2]);
1215 const char*
const connectString = argv[3];
1219 ASSERT_ALWAYS(mysql_init(&mysql));
1220 if(!mysql_real_connect(&mysql, host,
"root",
"",
"",
1222 printMySQLError(mysql,
"mysql_real_connect() failed:");
1223 return NDBT_ProgramExit(NDBT_FAILED);
1225 mySQLExec(mysql,
"create database if not exists CK_DB");
1226 mySQLExec(mysql,
"use CK_DB");
1229 if(con.
connect(12, 5, 1) != 0){
1230 ndbout <<
"Unable to connect to management server." << endl;
1231 return NDBT_ProgramExit(NDBT_FAILED);
1236 ndbout <<
"Cluster nodes not ready in 30 seconds." << endl;
1237 return NDBT_ProgramExit(NDBT_FAILED);
1239 Ndb ndb(&con,
"CK_DB");
1240 if(ndb.init() != 0){
1241 ERR(ndb.getNdbError());
1242 return NDBT_ProgramExit(NDBT_FAILED);
1244 runTestSuite(mysql, ndb);