diff options
Diffstat (limited to 'storage/ndb/test/odbc/driver/testOdbcDriver.cpp')
-rw-r--r-- | storage/ndb/test/odbc/driver/testOdbcDriver.cpp | 4964 |
1 files changed, 4964 insertions, 0 deletions
diff --git a/storage/ndb/test/odbc/driver/testOdbcDriver.cpp b/storage/ndb/test/odbc/driver/testOdbcDriver.cpp new file mode 100644 index 00000000000..d3b3802ebe1 --- /dev/null +++ b/storage/ndb/test/odbc/driver/testOdbcDriver.cpp @@ -0,0 +1,4964 @@ +/* Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + * testOdbcDriver + * + * Test of ODBC and SQL using a fixed set of tables. + */ + +#include <ndb_global.h> +#undef test +#include <ndb_version.h> +#include <kernel/ndb_limits.h> +#include <Bitmask.hpp> +#include <kernel/AttributeList.hpp> +#ifdef ndbODBC +#include <NdbApi.hpp> +#endif +#include <sqlext.h> + +#undef BOOL + +#include <NdbMain.h> +#include <NdbOut.hpp> +#include <NdbThread.h> +#include <NdbMutex.h> +#include <NdbCondition.h> +#include <NdbTick.h> +#include <NdbSleep.h> + +#ifdef ndbODBC +#include <NdbTest.hpp> +#else +#define NDBT_OK 0 +#define NDBT_FAILED 1 +#define NDBT_WRONGARGS 2 +static int +NDBT_ProgramExit(int rcode) +{ + const char* rtext = "Unknown"; + switch (rcode) { + case NDBT_OK: + rtext = "OK"; + break; + case NDBT_FAILED: + rtext = "Failed"; + break; + case NDBT_WRONGARGS: + rtext = "Wrong arguments"; + break; + }; + ndbout_c("\nNDBT_ProgramExit: %d - %s\n", rcode, rtext); + return rcode; +} +#endif + +#ifdef DMALLOC +#include <dmalloc.h> +#endif + +#define arraySize(x) (sizeof(x)/sizeof(x[0])) + +#define SQL_ATTR_NDB_TUPLES_FETCHED 66601 + +// options + +#define MAX_THR 128 // max threads + +struct Opt { + const char* m_name[100]; + unsigned m_namecnt; + bool m_core; + unsigned m_depth; + const char* m_dsn; + unsigned m_errs; + const char* m_fragtype; + unsigned m_frob; + const char* m_home; + unsigned m_loop; + bool m_nogetd; + bool m_noputd; + bool m_nosort; + unsigned m_scale; + bool m_serial; + const char* m_skip[100]; + unsigned m_skipcnt; + unsigned m_subloop; + const char* m_table; + unsigned m_threads; + unsigned m_trace; + unsigned m_v; + Opt() : + m_namecnt(0), + m_core(false), + m_depth(5), + m_dsn("NDB"), + m_errs(0), + m_fragtype(0), + m_frob(0), + m_home(0), + m_loop(1), + m_nogetd(false), + m_noputd(false), + m_nosort(false), + m_scale(100), + m_serial(false), + m_skipcnt(0), + m_subloop(1), + m_table(0), + m_threads(1), + m_trace(0), + m_v(1) { + for (unsigned i = 0; i < arraySize(m_name); i++) + m_name[i] = 0; + for (unsigned i = 0; i < arraySize(m_skip); i++) + m_skip[i] = 0; + } +}; + +static Opt opt; + +static void listCases(); +static void listTables(); +static void printusage() +{ + Opt d; + ndbout + << "usage: testOdbcDriver [options]" << endl + << "-case name run only named tests (substring match - can be repeated)" << endl + << "-core dump core on failure" << endl + << "-depth N join depth - default " << d.m_depth << endl + << "-dsn string data source name - default " << d.m_dsn << endl + << "-errs N allow N errors before quitting - default " << d.m_errs << endl + << "-fragtype t fragment type single/small/medium/large" << d.m_errs << endl + << "-frob X case-dependent tweak (number)" << endl + << "-home dir set NDB_HOME (contains Ndb.cfg)" << endl + << "-loop N loop N times (0 = forever) - default " << d.m_loop << endl + << "-nogetd do not use SQLGetData - default " << d.m_nogetd << endl + << "-noputd do not use SQLPutData - default " << d.m_noputd << endl + << "-nosort no order-by in verify scan (checks non-Pk values only)" << endl + << "-scale N row count etc - default " << d.m_scale << endl + << "-serial run multi-threaded test cases one at a time" << endl + << "-skip name skip named tests (substring match - can be repeated)" << endl + << "-subloop N loop count per case (same threads) - default " << d.m_subloop << endl + << "-table T do only table T (table name on built-in list)" << endl + << "-threads N number of threads (max " << MAX_THR << ") - default " << d.m_threads << endl + << "-trace N trace in NDB ODBC driver - default " << d.m_trace << endl + << "-v N verbosity - default " << d.m_v << endl + ; + listCases(); + listTables(); +} + +static void +fatal(const char* fmt, ...) +{ + va_list ap; + char buf[200]; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + ndbout << buf << endl; + if (opt.m_errs != 0) { + opt.m_errs--; + return; + } + if (opt.m_core) + abort(); + NDBT_ProgramExit(NDBT_FAILED); + exit(1); +} + +static void +cleanprint(const char* s, unsigned n) +{ + for (unsigned i = 0; i < n; i++) { + char b[10]; + if (0x20 < s[i] && s[i] <= 0x7e) + sprintf(b, "%c", s[i]); + else + sprintf(b, "\\%02x", (unsigned)s[i]); + ndbout << b; + } +} + +// global mutex +static NdbMutex my_mutex = NDB_MUTEX_INITIALIZER; +static void lock_mutex() { NdbMutex_Lock(&my_mutex); } +static void unlock_mutex() { NdbMutex_Unlock(&my_mutex); } + +// semaphore zeroed before each call to a test routine +static unsigned my_sema = 0; + +// print mutex +static NdbMutex out_mutex = NDB_MUTEX_INITIALIZER; +static NdbOut& lock(NdbOut& out) { NdbMutex_Lock(&out_mutex); return out; } +static NdbOut& unlock(NdbOut& out) { NdbMutex_Unlock(&out_mutex); return out; } + +static unsigned +urandom(unsigned n) +{ + assert(n != 0); + unsigned i = random(); + return i % n; +} + +// test cases + +struct Test; + +struct Case { + enum Mode { + Single = 1, // single thread + Serial = 2, // all threads but one at a time + Thread = 3 // all threads in parallel + }; + const char* m_name; + void (*m_func)(Test& test); + Mode m_mode; + unsigned m_stuff; + const char* m_desc; + Case(const char* name, void (*func)(Test& test), Mode mode, unsigned stuff, const char* desc) : + m_name(name), + m_func(func), + m_mode(mode), + m_stuff(stuff), + m_desc(desc) { + } + const char* modename() const { + const char* s = "?"; + if (m_mode == Case::Single) + return "Single"; + if (m_mode == Case::Serial) + return "Serial"; + if (m_mode == Case::Thread) + return "Thread"; + return "?"; + } + bool matchcase() const { + if (opt.m_namecnt == 0) + return ! skipcase(); + for (unsigned i = 0; i < opt.m_namecnt; i++) { + if (strstr(m_name, opt.m_name[i]) != 0) + return ! skipcase(); + } + return false; + } +private: + bool skipcase() const { + for (unsigned i = 0; i < opt.m_skipcnt; i++) { + if (strstr(m_name, opt.m_skip[i]) != 0) + return true; + } + return false; + } +}; + +// calculate values + +struct Calc { + enum { m_mul = 1000000 }; + unsigned m_no; + unsigned m_base; + unsigned m_salt; // modifies non-PK values + bool m_const; // base non-PK values on PK of row 0 + Calc(unsigned no) : + m_no(no), + m_salt(0), + m_const(false) { + m_base = m_no * m_mul; + } + void calcPk(unsigned rownum, char* v, unsigned n) const { + char b[10]; + sprintf(b, "%08x", m_base + rownum); + for (unsigned i = 0; i < n; i++) { + char c = i < n - 1 ? b[i % 8] : 0; + v[i] = c; + } + } + void calcPk(unsigned rownum, long* v) const { + *v = m_base + rownum; + } + void hashPk(unsigned* hash, const char* v, unsigned n) const { + for (unsigned i = 0; i < n; i++) { + *hash ^= (v[i] << i); + } + } + void hashPk(unsigned* hash, long v) const { + *hash ^= v; + } + void calcNk(unsigned hash, char* v, unsigned n, SQLINTEGER* ind, bool null) const { + unsigned m = hash % n; + for (unsigned i = 0; i < n; i++) { + char c = i < m ? 'a' + (hash + i) % ('z' - 'a' + 1) : i < n - 1 ? ' ' : 0; + v[i] = c; + } + *ind = null && hash % 9 == 0 ? SQL_NULL_DATA : SQL_NTS; + } + void calcNk(unsigned hash, long* v, SQLINTEGER* ind, bool null) const { + *v = long(hash); + *ind = null && hash % 7 == 0 ? SQL_NULL_DATA : 0; + } + void calcNk(unsigned hash, double* v, SQLINTEGER* ind, bool null) const { + *v = long(hash) / 1000.0; + *ind = null && hash % 5 == 0 ? SQL_NULL_DATA : 0; + } + bool verify(const char* v1, SQLINTEGER ind1, const char* v2, SQLINTEGER ind2, unsigned n) { + if (ind1 == SQL_NULL_DATA && ind2 == SQL_NULL_DATA) + return true; + if (ind1 != SQL_NULL_DATA && ind2 != SQL_NULL_DATA) + if (memcmp(v1, v2, n) == 0) + return true; + if (ind1 == SQL_NULL_DATA) + v1 = "NULL"; + if (ind2 == SQL_NULL_DATA) + v2 = "NULL"; + ndbout << "verify failed: got "; + if (ind1 == SQL_NULL_DATA) + ndbout << "NULL"; + else + cleanprint(v1, n); + ndbout << " != "; + if (ind2 == SQL_NULL_DATA) + ndbout << "NULL"; + else + cleanprint(v2, n); + ndbout << endl; + return false; + } + bool verify(long v1, SQLINTEGER ind1, long v2, SQLINTEGER ind2) { + char buf1[40], buf2[40]; + if (ind1 == SQL_NULL_DATA && ind2 == SQL_NULL_DATA) + return true; + if (ind1 != SQL_NULL_DATA && ind2 != SQL_NULL_DATA) + if (v1 == v2) + return true; + if (ind1 == SQL_NULL_DATA) + strcpy(buf1, "NULL"); + else + sprintf(buf1, "%ld", v1); + if (ind2 == SQL_NULL_DATA) + strcpy(buf2, "NULL"); + else + sprintf(buf2, "%ld", v2); + ndbout << "verify failed: got " << buf1 << " != " << buf2 << endl; + return false; + } + bool verify(double v1, SQLINTEGER ind1, double v2, SQLINTEGER ind2) { + char buf1[40], buf2[40]; + if (ind1 == SQL_NULL_DATA && ind2 == SQL_NULL_DATA) + return true; + if (ind1 != SQL_NULL_DATA && ind2 != SQL_NULL_DATA) + if (fabs(v1 - v2) < 1) // XXX + return true; + if (ind1 == SQL_NULL_DATA) + strcpy(buf1, "NULL"); + else + sprintf(buf1, "%.10f", v1); + if (ind2 == SQL_NULL_DATA) + strcpy(buf2, "NULL"); + else + sprintf(buf2, "%.10f", v2); + ndbout << "verify failed: got " << buf1 << " != " << buf2 << endl; + return false; + } +}; + +#if defined(NDB_SOLARIS) || defined(NDB_LINUX) || defined(NDB_MACOSX) +#define HAVE_SBRK +#else +#undef HAVE_SBRK +#endif + +struct Timer { + Timer() : + m_cnt(0), + m_calls(0), + m_on(0), + m_msec(0) +#ifndef NDB_WIN32 + , + m_brk(0), + m_incr(0) +#endif + { + } + void timerOn() { + m_cnt = 0; + m_calls = 0; + m_on = NdbTick_CurrentMillisecond(); +#ifdef HAVE_SBRK + m_brk = (int)sbrk(0); +#endif + } + void timerOff() { + m_msec = NdbTick_CurrentMillisecond() - m_on; + if (m_msec <= 0) + m_msec = 1; +#ifdef HAVE_SBRK + m_incr = (int)sbrk(0) - m_brk; + if (m_incr < 0) + m_incr = 0; +#endif + } + void timerCnt(unsigned cnt) { + m_cnt += cnt; + } + void timerCnt(const Timer& timer) { + m_cnt += timer.m_cnt; + m_calls += timer.m_calls; + } + friend NdbOut& operator<<(NdbOut& out, const Timer& timer) { + out << timer.m_cnt << " ( " << 1000 * timer.m_cnt / timer.m_msec << "/sec )"; +#ifdef HAVE_SBRK + out << " - " << timer.m_incr << " sbrk"; + if (opt.m_namecnt != 0) { // per case meaningless if many cases + if (timer.m_cnt > 0) + out << " ( " << timer.m_incr / timer.m_cnt << "/cnt )"; + } +#endif + out << " - " << timer.m_calls << " calls"; + return out; + } +protected: + unsigned m_cnt; // count rows or whatever + unsigned m_calls; // count ODBC function calls + NDB_TICKS m_on; + unsigned m_msec; +#ifdef HAVE_SBRK + int m_brk; + int m_incr; +#endif +}; + +#define MAX_MESSAGE 500 +#define MAX_DIAG 20 + +struct Diag { + char m_state[5+1]; + SQLINTEGER m_native; + char m_message[MAX_MESSAGE]; + unsigned m_flag; // temp use + Diag() { + strcpy(m_state, "00000"); + m_native = 0; + memset(m_message, 0, sizeof(m_message)); + m_flag = 0; + } + const char* text() { + snprintf(m_buf, sizeof(m_buf), "%s %d '%s'", m_state, (int)m_native, m_message); + return m_buf; + } + void getDiag(SQLSMALLINT type, SQLHANDLE handle, unsigned k, unsigned count) { + int ret; + SQLSMALLINT length = -1; + memset(m_message, 0, MAX_MESSAGE); + ret = SQLGetDiagRec(type, handle, k, (SQLCHAR*)m_state, &m_native, (SQLCHAR*)m_message, MAX_MESSAGE, &length); + if (k <= count && ret != SQL_SUCCESS) + fatal("SQLGetDiagRec %d of %d: return %d != SQL_SUCCESS", k, count, (int)ret); + if (k <= count && strlen(m_message) != length) + fatal("SQLGetDiagRec %d of %d: message length %d != %d", k, count, strlen(m_message), length); + if (k > count && ret != SQL_NO_DATA) + fatal("SQLGetDiagRec %d of %d: return %d != SQL_NO_DATA", k, count, (int)ret); + m_flag = 0; + } +private: + char m_buf[MAX_MESSAGE]; +}; + +struct Diags { + Diag m_diag[MAX_DIAG]; + SQLINTEGER m_diagCount; + SQLINTEGER m_rowCount; + SQLINTEGER m_functionCode; + void getDiags(SQLSMALLINT type, SQLHANDLE handle) { + int ret; + m_diagCount = -1; + ret = SQLGetDiagField(type, handle, 0, SQL_DIAG_NUMBER, &m_diagCount, SQL_IS_INTEGER, 0); + if (ret == SQL_INVALID_HANDLE) + return; + if (ret != SQL_SUCCESS) + fatal("SQLGetDiagField: return %d != SQL_SUCCESS", (int)ret); + if (m_diagCount < 0 || m_diagCount > MAX_DIAG) + fatal("SQLGetDiagField: count %d", (int)m_diagCount); + for (unsigned k = 0; k < MAX_DIAG; k++) { + m_diag[k].getDiag(type, handle, k + 1, m_diagCount); + if (k == m_diagCount + 1) + break; + } + m_rowCount = -1; + m_functionCode = SQL_DIAG_UNKNOWN_STATEMENT; + if (type == SQL_HANDLE_STMT) { + ret = SQLGetDiagField(type, handle, 0, SQL_DIAG_ROW_COUNT, &m_rowCount, SQL_IS_INTEGER, 0); +#ifndef iODBC + if (ret != SQL_SUCCESS) + fatal("SQLGetDiagField: return %d != SQL_SUCCESS", (int)ret); +#endif + ret = SQLGetDiagField(type, handle, 0, SQL_DIAG_DYNAMIC_FUNCTION_CODE, &m_functionCode, SQL_IS_INTEGER, 0); + } + } + void showDiags() { + for (unsigned k = 0; 0 <= m_diagCount && k < m_diagCount; k++) { + Diag& diag = m_diag[k]; + ndbout << "diag " << k + 1; + ndbout << (diag.m_flag ? " [*]" : " [ ]"); + ndbout << " " << diag.text() << endl; + if (k > 10) + abort(); + } + } +}; + +struct Exp { + int m_ret; + const char* m_state; + SQLINTEGER m_native; + Exp() : m_ret(SQL_SUCCESS), m_state(""), m_native(0) {} + Exp(int ret, const char* state) : m_ret(ret), m_state(state) {} +}; + +struct Test : Calc, Timer, Diags { + Test(unsigned no, unsigned loop) : + Calc(no), + m_loop(loop), + m_stuff(0), + m_perf(false), + ccp(0) { + exp(SQL_SUCCESS, 0, 0, true); + } + unsigned m_loop; // current loop + Exp m_expList[20]; // expected results + unsigned m_expCount; + int m_ret; // actual return code + int m_stuff; // the stuff of abuse + bool m_perf; // check no diags on success + const Case* ccp; // current case + void exp(int ret, const char* state, SQLINTEGER native, bool reset) { + if (reset) + m_expCount = 0; + unsigned i = m_expCount++; + assert(i < arraySize(m_expList) - 1); + m_expList[i].m_ret = ret; + m_expList[i].m_state = state == 0 ? "" : state; + m_expList[i].m_native = native; + } + void runCase(const Case& cc) { + ccp = &cc; + if (opt.m_v >= 3) + ndbout << cc.m_name << ": start" << endl; + m_rowCount = -1; + NDB_TICKS m_ms1 = NdbTick_CurrentMillisecond(); + m_salt = m_loop | (16 << cc.m_stuff); + m_const = cc.m_stuff == 0; + m_stuff = cc.m_stuff; + (*cc.m_func)(*this); + NDB_TICKS m_ms2 = NdbTick_CurrentMillisecond(); + } + void run(SQLSMALLINT type, SQLHANDLE handle, int line, int ret) { + m_calls++; + m_ret = ret; + if (m_perf && (m_ret == SQL_SUCCESS)) + return; + m_diagCount = 0; + if (handle != SQL_NULL_HANDLE) + getDiags(type, handle); + if (m_diagCount <= 0 && (ret != SQL_SUCCESS && ret != SQL_INVALID_HANDLE && ret != SQL_NEED_DATA && ret != SQL_NO_DATA)) { + fatal("%s: thr %d line %d: ret=%d but no diag records", ccp->m_name, m_no, line, ret); + } + for (unsigned k = 0; 0 <= m_diagCount && k < m_diagCount; k++) { + Diag& diag = m_diag[k]; + bool match = false; + for (unsigned i = 0; i < m_expCount; i++) { + if (strcmp(diag.m_state, m_expList[i].m_state) == 0 && (diag.m_native % 10000 == m_expList[i].m_native % 10000 || m_expList[i].m_native == -1)) { + match = true; + diag.m_flag = 0; + continue; + } + diag.m_flag = 1; // mark unexpected + } + if (! match) { + showDiags(); + fatal("%s: thr %d line %d: unexpected diag [*] ret=%d cnt=%d", ccp->m_name, m_no, line, (int)ret, (int)m_diagCount); + } + } + bool match = false; + for (unsigned i = 0; i < m_expCount; i++) { + if (ret == m_expList[i].m_ret) { + match = true; + break; + } + } + if (! match) { + showDiags(); + fatal("%s: thr %d line %d: ret=%d not expected", ccp->m_name, m_no, line, ret); + } + // reset expected to success + exp(SQL_SUCCESS, 0, 0, true); + } + void chk(SQLSMALLINT type, SQLHANDLE handle, int line, bool match, const char* fmt, ...) { + if (match) + return; + va_list ap; + va_start(ap, fmt); + char buf[500]; + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + fatal("%s: thr %d line %d: check failed - %s", ccp->m_name, m_no, line, buf); + } +}; + +#define HNull 0, SQL_NULL_HANDLE, __LINE__ +#define HEnv(h) SQL_HANDLE_ENV, h, __LINE__ +#define HDbc(h) SQL_HANDLE_DBC, h, __LINE__ +#define HStmt(h) SQL_HANDLE_STMT, h, __LINE__ +#define HDesc(h) SQL_HANDLE_DESC, h, __LINE__ + +// string support + +#define MAX_SQL 20000 + +static void +scopy(char*& ptr, const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsprintf(ptr, fmt, ap); + va_end(ap); + ptr += strlen(ptr); +} + +static bool +blankeq(const char* s1, const char* s2, bool caseSensitive = false) +{ + unsigned n1 = strlen(s1); + unsigned n2 = strlen(s2); + unsigned i = 0; + char c1 = 0; + char c2 = 0; + while (i < n1 || i < n2) { + c1 = i < n1 ? s1[i] : 0x20; + if (! caseSensitive && 'a' <= c1 && c1 <= 'z') + c1 -= 'a' - 'A'; + c2 = i < n2 ? s2[i] : 0x20; + if (! caseSensitive && 'a' <= c2 && c2 <= 'z') + c2 -= 'a' - 'A'; + if (c1 != c2) + break; + i++; + } + return c1 == c2; +} + +// columns and tables + +struct Col { + enum Type { + Char = SQL_CHAR, + Varchar = SQL_VARCHAR, + Int = SQL_INTEGER, + Bigint = SQL_BIGINT, + Real = SQL_REAL, + Double = SQL_DOUBLE + }; + enum CType { + CChar = SQL_C_CHAR, + CLong = SQL_C_SLONG, + CDouble = SQL_C_DOUBLE + }; + enum Cons { + Null, // nullable + NotNull, // not nullable + Pk // part of primary key + }; + const char* m_name; + Type m_type; + unsigned m_length; + Cons m_cons; + CType m_ctype; + Col() : + m_type((Type)999) { + } + Col(const char* name, Type type, unsigned length, Cons cons, CType ctype) : + m_name(name), + m_type(type), + m_length(length), + m_cons(cons), + m_ctype(ctype) { + } + unsigned size() const { + switch (m_type) { + case Char: + case Varchar: + return m_length; + case Int: + return 4; + case Bigint: + return 8; + case Real: + return 4; + case Double: + return 8; + } + assert(false); + return 0; + } + unsigned csize() const { // size as char plus terminating null + switch (m_ctype) { + case CChar: + return m_length + 1; + case CLong: + return 12; + case CDouble: + return 24; + } + assert(false); + return 0; + } + void typespec(char*& ptr) const { + switch (m_type) { + case Char: + scopy(ptr, "char(%d)", m_length); + return; + case Varchar: + scopy(ptr, "varchar(%d)", m_length); + return; + case Int: + scopy(ptr, "int"); + return; + case Bigint: + scopy(ptr, "bigint"); + return; + case Real: + scopy(ptr, "real"); + return; + case Double: + scopy(ptr, "float"); + return; + } + assert(false); + } + SQLSMALLINT type() const { + return (SQLSMALLINT)m_type; + } + SQLSMALLINT ctype() const { + return (SQLSMALLINT)m_ctype; + } + void create(char*& ptr, bool pk) const { + scopy(ptr, "%s", m_name); + scopy(ptr, " "); + typespec(ptr); + if (m_cons == Pk && pk) { + scopy(ptr, " primary key"); + } + if (m_cons == NotNull) { + scopy(ptr, " not null"); + } + } +}; + +static Col ColUndef; + +struct Tab { + const char* m_name; + const Col* m_colList; + unsigned m_colCount; + unsigned m_pkCount; + unsigned* m_pkIndex; + unsigned m_nkCount; + unsigned* m_nkIndex; + char m_upperName[20]; + Tab(const char* name, const Col* colList, unsigned colCount) : + m_name(name), + m_colList(colList), + m_colCount(colCount) { + m_pkCount = 0; + m_nkCount = 0; + for (unsigned i = 0; i < m_colCount; i++) { + const Col& col = m_colList[i]; + if (col.m_cons == Col::Pk) + m_pkCount++; + else + m_nkCount++; + } + m_pkIndex = new unsigned[m_pkCount]; + m_nkIndex = new unsigned[m_nkCount]; + unsigned pk = 0; + unsigned nk = 0; + for (unsigned i = 0; i < m_colCount; i++) { + const Col& col = m_colList[i]; + if (col.m_cons == Col::Pk) + m_pkIndex[pk++] = i; + else + m_nkIndex[nk++] = i; + } + assert(pk == m_pkCount && nk == m_nkCount); + strcpy(m_upperName, m_name); + for (char* p = m_upperName; *p != 0; p++) { + if ('a' <= *p && *p <= 'z') + *p -= 'a' - 'A'; + } + } + ~Tab() { + delete[] m_pkIndex; + delete[] m_nkIndex; + } + void drop(char*& ptr) const { + scopy(ptr, "drop table %s", m_name); + } + void create(char*& ptr) const { + scopy(ptr, "create table %s (", m_name); + for (unsigned i = 0; i < m_colCount; i++) { + if (i > 0) + scopy(ptr, ", "); + const Col& col = m_colList[i]; + col.create(ptr, m_pkCount == 1); + } + if (m_pkCount != 1) { + scopy(ptr, ", primary key ("); + for (unsigned i = 0; i < m_pkCount; i++) { + const Col& col = m_colList[m_pkIndex[i]]; + if (i > 0) + scopy(ptr, ", "); + scopy(ptr, "%s", col.m_name); + } + scopy(ptr, ")"); + } + scopy(ptr, ")"); + } + void wherePk(char*& ptr) const { + scopy(ptr, " where"); + for (unsigned i = 0; i < m_pkCount; i++) { + const Col& col = m_colList[m_pkIndex[i]]; + if (i > 0) + scopy(ptr, " and"); + scopy(ptr, " %s = ?", col.m_name); + } + } + void whereRange(char*& ptr) const { + scopy(ptr, " where"); + for (unsigned i = 0; i < m_pkCount; i++) { + const Col& col = m_colList[m_pkIndex[i]]; + if (i > 0) + scopy(ptr, " and"); + scopy(ptr, " ? <= %s", col.m_name); + scopy(ptr, " and "); + scopy(ptr, "%s < ?", col.m_name); + } + } + void orderPk(char*& ptr) const { + scopy(ptr, " order by"); + for (unsigned i = 0; i < m_pkCount; i++) { + const Col& col = m_colList[m_pkIndex[i]]; + if (i > 0) + scopy(ptr, ", "); + else + scopy(ptr, " "); + scopy(ptr, "%s", col.m_name); + } + } + void selectPk(char*& ptr) const { + scopy(ptr, "select * from %s", m_name); + wherePk(ptr); + } + void selectAll(char*& ptr) const { + scopy(ptr, "select * from %s", m_name); + } + void selectRange(char*& ptr, bool sort) const { + selectAll(ptr); + whereRange(ptr); + if (sort) + orderPk(ptr); + } + void selectCount(char*& ptr) const { + scopy(ptr, "select count(*) from %s", m_name); + } + void insertAll(char*& ptr) const { + scopy(ptr, "insert into %s values (", m_name); + for (unsigned i = 0; i < m_colCount; i++) { + if (i > 0) + scopy(ptr, ", "); + scopy(ptr, "?"); + } + scopy(ptr, ")"); + } + void updatePk(char*& ptr) const { + scopy(ptr, "update %s set", m_name); + for (unsigned i = 0; i < m_nkCount; i++) { + const Col& col = m_colList[m_nkIndex[i]]; + if (i > 0) + scopy(ptr, ", "); + else + scopy(ptr, " "); + scopy(ptr, "%s = ?", col.m_name); + } + wherePk(ptr); + } + void updateRange(char*& ptr) const { + scopy(ptr, "update %s set", m_name); + for (unsigned i = 0; i < m_nkCount; i++) { + const Col& col = m_colList[m_nkIndex[i]]; + if (i > 0) + scopy(ptr, ", "); + else + scopy(ptr, " "); + scopy(ptr, "%s = ?", col.m_name); // XXX constant for now + } + whereRange(ptr); + } + void deleteAll(char*& ptr) const { + scopy(ptr, "delete from %s", m_name); + } + void deletePk(char*& ptr) const { + scopy(ptr, "delete from %s", m_name); + wherePk(ptr); + } + void deleteRange(char*& ptr) const { + scopy(ptr, "delete from %s", m_name); + whereRange(ptr); + } + // simple + void insertDirect(char*& ptr, unsigned n) const { + scopy(ptr, "insert into %s values (", m_name); + for (unsigned i = 0; i < m_colCount; i++) { + const Col& col = m_colList[i]; + if (i > 0) + scopy(ptr, ", "); + if (col.m_type == Col::Char || col.m_type == Col::Varchar) { + scopy(ptr, "'"); + for (unsigned i = 0; i <= n % col.m_length; i++) + scopy(ptr, "%c", 'a' + (n + i) % 26); + scopy(ptr, "'"); + } else if (col.m_type == Col::Int || col.m_type == Col::Bigint) { + scopy(ptr, "%u", n); + } else if (col.m_type == Col::Real || col.m_type == Col::Double) { + scopy(ptr, "%.3f", n * 0.001); + } else { + assert(false); + } + } + scopy(ptr, ")"); + } + void whereDirect(char*& ptr, unsigned n) const { + scopy(ptr, " where"); + for (unsigned i = 0; i < m_pkCount; i++) { + const Col& col = m_colList[m_pkIndex[i]]; + if (i > 0) + scopy(ptr, ", "); + else + scopy(ptr, " "); + scopy(ptr, "%s = ", col.m_name); + if (col.m_type == Col::Char || col.m_type == Col::Varchar) { + scopy(ptr, "'"); + for (unsigned i = 0; i <= n % col.m_length; i++) + scopy(ptr, "%c", 'a' + (n + i) % 26); + scopy(ptr, "'"); + } else if (col.m_type == Col::Int || col.m_type == Col::Bigint) { + scopy(ptr, "%u", n); + } else { + assert(false); + } + } + } + void countDirect(char*& ptr, unsigned n) const { + scopy(ptr, "select count(*) from %s", m_name); + whereDirect(ptr, n); + } + void deleteDirect(char*& ptr, unsigned n) const { + scopy(ptr, "delete from %s", m_name); + whereDirect(ptr, n); + } + // joins + void selectCart(char*& ptr, unsigned cnt) const { + scopy(ptr, "select count(*) from"); + for (unsigned j = 0; j < cnt; j++) { + if (j > 0) + scopy(ptr, ","); + scopy(ptr, " %s", m_name); + scopy(ptr, " t%u", j); + } + } + void selectJoin(char*& ptr, unsigned cnt) const { + scopy(ptr, "select * from"); + for (unsigned j = 0; j < cnt; j++) { + if (j > 0) + scopy(ptr, ","); + scopy(ptr, " %s", m_name); + scopy(ptr, " t%u", j); + } + for (unsigned i = 0; i < m_pkCount; i++) { + const Col& col = m_colList[m_pkIndex[i]]; + for (unsigned j = 0; j < cnt - 1; j++) { + if (i == 0 && j == 0) + scopy(ptr, " where"); + else + scopy(ptr, " and"); + scopy(ptr, " t%u.%s = t%u.%s", j, col.m_name, j + 1, col.m_name); + } + } + } + // check if selected on command line + bool optok() const { + return opt.m_table == 0 || strcasecmp(m_name, opt.m_table) == 0; + } +}; + +// the test tables + +static Col col0[] = { + Col( "a", Col::Bigint, 0, Col::Pk, Col::CLong ), + Col( "b", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c", Col::Char, 4, Col::NotNull, Col::CChar ), + Col( "d", Col::Double, 0, Col::Null, Col::CDouble ), + Col( "e", Col::Char, 40, Col::Null, Col::CChar ), + Col( "f", Col::Char, 10, Col::Null, Col::CChar ) +}; + +static Col col1[] = { + Col( "c0", Col::Int, 0, Col::Pk, Col::CLong ), + Col( "c1", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c2", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c3", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c4", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c5", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c6", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c7", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c8", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c9", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c10", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c11", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c12", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c13", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c14", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c15", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c16", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c17", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c18", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c19", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c20", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c21", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c22", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c23", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c24", Col::Int, 0, Col::NotNull, Col::CLong ), + Col( "c25", Col::Int, 0, Col::NotNull, Col::CLong ) +}; + +static Col col2[] = { + Col( "a", Col::Int, 0, Col::Pk, Col::CLong ), + Col( "c", Col::Char, 8000, Col::NotNull, Col::CChar ) +}; + +static Col col3[] = { + Col( "a", Col::Int, 0, Col::Pk, Col::CLong ), + Col( "c1", Col::Varchar, 1, Col::Null, Col::CChar ), + Col( "c2", Col::Varchar, 2, Col::Null, Col::CChar ), + Col( "c3", Col::Varchar, 3, Col::Null, Col::CChar ), + Col( "c4", Col::Varchar, 4, Col::Null, Col::CChar ), + Col( "c5", Col::Varchar, 10, Col::Null, Col::CChar ), + Col( "c6", Col::Varchar, 40, Col::Null, Col::CChar ), + Col( "c7", Col::Varchar, 255, Col::Null, Col::CChar ), + Col( "c8", Col::Varchar, 4000, Col::Null, Col::CChar ) +}; + +static Col col4[] = { + Col( "a", Col::Char, 8, Col::Pk, Col::CChar ), + Col( "b", Col::Char, 8, Col::NotNull, Col::CChar ), +}; + +static Tab tabList[] = { +#define colList(x) x, arraySize(x) + Tab( "tt00", colList(col0) ), + Tab( "tt01", colList(col1) ), // fläskbench special + Tab( "tt02", colList(col2) ), + Tab( "tt03", colList(col3) ), + Tab( "tt04", colList(col4) ) +#undef colList +}; + +static const unsigned tabCount = arraySize(tabList); +static const unsigned maxColCount = 100; // per table - keep up to date + +static bool +findTable() +{ + for (unsigned i = 0; i < tabCount; i++) { + const Tab& tab = tabList[i]; + if (tab.optok()) + return true; + } + return false; +} + +static void +listTables() +{ + ndbout << "tables:" << endl; + for (unsigned i = 0; i < tabCount; i++) { + const Tab& tab = tabList[i]; + if (i > 0) + ndbout << " "; + ndbout << tab.m_name; + } + ndbout << endl; +} + +// data fields and rows + +struct Fld { + const Col& m_col; + union { + char* m_char; + long m_long; + double m_double; + }; + SQLINTEGER m_ind; + SQLINTEGER m_need; // constant + Fld() : + m_col(ColUndef), + m_need(0) { + } + Fld(const Col& col) : + m_col(col), + m_need(SQL_LEN_DATA_AT_EXEC(0)) { + switch (m_col.m_ctype) { + case Col::CChar: + m_char = new char[m_col.csize()]; + memset(m_char, 0, m_col.csize()); + break; + case Col::CLong: + m_long = 0; + break; + case Col::CDouble: + m_double = 0.0; + break; + } + m_ind = -1; + } + ~Fld() { + switch (m_col.m_ctype) { + case Col::CChar: + delete[] m_char; + break; + case Col::CLong: + break; + case Col::CDouble: + break; + } + } + void zero() { + switch (m_col.m_ctype) { + case Col::CChar: + memset(m_char, 0x1f, m_col.csize()); + break; + case Col::CLong: + m_long = 0x1f1f1f1f; + break; + case Col::CDouble: + m_double = 1111111.1111111; + break; + } + m_ind = -1; + } + // copy values from another field + void copy(const Fld& fld) { + assert(&m_col == &fld.m_col); + switch (m_col.m_ctype) { + case Col::CChar: + memcpy(m_char, fld.m_char, m_col.csize()); + break; + case Col::CLong: + m_long = fld.m_long; + break; + case Col::CDouble: + m_double = fld.m_double; + break; + default: + assert(false); + break; + } + m_ind = fld.m_ind; + } + SQLPOINTER caddr() { + switch (m_col.m_ctype) { + case Col::CChar: + return (SQLPOINTER)m_char; + case Col::CLong: + return (SQLPOINTER)&m_long; + case Col::CDouble: + return (SQLPOINTER)&m_double; + } + assert(false); + return 0; + } + SQLINTEGER* ind() { + return &m_ind; + } + SQLINTEGER* need() { + m_need = SQL_LEN_DATA_AT_EXEC(0); + return &m_need; + } + void calcPk(const Test& test, unsigned rownum) { + switch (m_col.m_ctype) { + case Col::CChar: + test.calcPk(rownum, m_char, m_col.csize()); + m_ind = SQL_NTS; + return; + case Col::CLong: + test.calcPk(rownum, &m_long); + m_ind = 0; + return; + case Col::CDouble: + assert(false); + return; + } + assert(false); + } + void hashPk(const Test& test, unsigned* hash) const { + switch (m_col.m_ctype) { + case Col::CChar: + test.hashPk(hash, m_char, m_col.csize()); + return; + case Col::CLong: + test.hashPk(hash, m_long); + return; + case Col::CDouble: + assert(false); + return; + } + assert(false); + } + void calcNk(const Test& test, unsigned hash) { + bool null = m_col.m_cons == Col::Null; + switch (m_col.m_ctype) { + case Col::CChar: + test.calcNk(hash, m_char, m_col.csize(), &m_ind, null); + return; + case Col::CLong: + test.calcNk(hash, &m_long, &m_ind, null); + return; + case Col::CDouble: + test.calcNk(hash, &m_double, &m_ind, null); + return; + } + assert(false); + } + bool verify(Test& test, const Fld& fld) { + assert(&m_col == &fld.m_col); + switch (m_col.m_ctype) { + case Col::CChar: + return test.verify(m_char, m_ind, fld.m_char, fld.m_ind, m_col.csize()); + case Col::CLong: + return test.verify(m_long, m_ind, fld.m_long, fld.m_ind); + case Col::CDouble: + return test.verify(m_double, m_ind, fld.m_double, fld.m_ind); + } + assert(false); + return false; + } + // debug + void print() const { + if (m_ind == SQL_NULL_DATA) + ndbout << "NULL"; + else { + switch (m_col.m_ctype) { + case Col::CChar: + ndbout << m_char; + break; + case Col::CLong: + ndbout << (int)m_long; + break; + case Col::CDouble: + ndbout << m_double; + break; + } + } + } +}; + +struct Row { + const Tab& m_tab; + Fld* m_fldList; + Row(const Tab& tab) : + m_tab(tab) { + m_fldList = new Fld[m_tab.m_colCount]; + for (unsigned i = 0; i < m_tab.m_colCount; i++) { + const Col& col = m_tab.m_colList[i]; + void* place = &m_fldList[i]; + new (place) Fld(col); + } + } + ~Row() { + delete[] m_fldList; + } + // copy values from another row + void copy(const Row& row) { + assert(&m_tab == &row.m_tab); + for (unsigned i = 0; i < m_tab.m_colCount; i++) { + Fld& fld = m_fldList[i]; + fld.copy(row.m_fldList[i]); + } + } + // primary key value is determined by row number + void calcPk(Test& test, unsigned rownum) { + for (unsigned i = 0; i < m_tab.m_pkCount; i++) { + Fld& fld = m_fldList[m_tab.m_pkIndex[i]]; + fld.calcPk(test, rownum); + } + } + // other fields are determined by primary key value + void calcNk(Test& test) { + unsigned hash = test.m_salt; + for (unsigned i = 0; i < m_tab.m_pkCount; i++) { + Fld& fld = m_fldList[m_tab.m_pkIndex[i]]; + fld.hashPk(test, &hash); + } + for (unsigned i = 0; i < m_tab.m_colCount; i++) { + const Col& col = m_tab.m_colList[i]; + if (col.m_cons == Col::Pk) + continue; + Fld& fld = m_fldList[i]; + fld.calcNk(test, hash); + } + } + // verify against another row + bool verifyPk(Test& test, const Row& row) const { + assert(&m_tab == &row.m_tab); + for (unsigned i = 0; i < m_tab.m_pkCount; i++) { + Fld& fld = m_fldList[m_tab.m_pkIndex[i]]; + if (! fld.verify(test, row.m_fldList[m_tab.m_pkIndex[i]])) { + ndbout << "verify failed: tab=" << m_tab.m_name << " col=" << fld.m_col.m_name << endl; + return false; + } + } + return true; + } + bool verifyNk(Test& test, const Row& row) const { + assert(&m_tab == &row.m_tab); + for (unsigned i = 0; i < m_tab.m_nkCount; i++) { + Fld& fld = m_fldList[m_tab.m_nkIndex[i]]; + if (! fld.verify(test, row.m_fldList[m_tab.m_nkIndex[i]])) { + ndbout << "verify failed: tab=" << m_tab.m_name << " col=" << fld.m_col.m_name << endl; + return false; + } + } + return true; + } + bool verify(Test& test, const Row& row) const { + return verifyPk(test, row) && verifyNk(test, row); + } + // debug + void print() const { + ndbout << "row"; + for (unsigned i = 0; i < m_tab.m_colCount; i++) { + ndbout << " " << i << "="; + Fld& fld = m_fldList[i]; + fld.print(); + } + ndbout << endl; + } +}; + +// set ODBC version - required + +static void +setVersion(Test& test, SQLHANDLE hEnv) +{ + test.run(HEnv(hEnv), SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER)); +} + +// set autocommit + +static void +setAutocommit(Test& test, SQLHANDLE hDbc, bool on) +{ + SQLUINTEGER value = on ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF; + test.run(HDbc(hDbc), SQLSetConnectAttr(hDbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)value, SQL_IS_UINTEGER)); + SQLUINTEGER value2 = (SQLUINTEGER)-1; + test.run(HDbc(hDbc), SQLGetConnectAttr(hDbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)&value2, SQL_IS_UINTEGER, 0)); + test.chk(HDbc(hDbc), value2 == value, "got %u != %u", (unsigned)value2, (unsigned)value); +} + +// subroutines - migrate simple common routines here + +static void +allocEnv(Test& test, SQLHANDLE& hEnv) +{ + test.run(HNull, SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv)); + setVersion(test, hEnv); +} + +static void +allocDbc(Test& test, SQLHANDLE hEnv, SQLHANDLE& hDbc) +{ + test.run(HEnv(hEnv), SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc)); +} + +static void +allocConn(Test& test, SQLHANDLE hEnv, SQLHANDLE& hDbc) +{ + allocDbc(test, hEnv, hDbc); +#ifdef unixODBC + test.exp(SQL_SUCCESS_WITH_INFO, "IM003", 0, false); // unicode?? + test.exp(SQL_SUCCESS_WITH_INFO, "01000", 0, false); // version?? +#endif + test.run(HDbc(hDbc), SQLConnect(hDbc, (SQLCHAR*)opt.m_dsn, SQL_NTS, (SQLCHAR*)"user", SQL_NTS, (SQLCHAR*)"pass", SQL_NTS)); +} + +static void +allocStmt(Test& test, SQLHANDLE hDbc, SQLHANDLE& hStmt) +{ + test.run(HDbc(hDbc), SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt)); +} + +static void +allocAll(Test& test, SQLHANDLE& hEnv, SQLHANDLE& hDbc, SQLHANDLE& hStmt) +{ + allocEnv(test, hEnv); + allocConn(test, hEnv, hDbc); + allocStmt(test, hDbc, hStmt); +} + +static void +allocAll(Test& test, SQLHANDLE& hEnv, SQLHANDLE& hDbc, SQLHANDLE* hStmtList, unsigned nStmt) +{ + allocEnv(test, hEnv); + allocConn(test, hEnv, hDbc); + for (unsigned i = 0; i < nStmt; i++) + allocStmt(test, hDbc, hStmtList[i]); +} + +static void +freeEnv(Test& test, SQLHANDLE hEnv) +{ + test.run(HNull, SQLFreeHandle(SQL_HANDLE_ENV, hEnv)); +} + +static void +freeDbc(Test& test, SQLHANDLE hEnv, SQLHANDLE hDbc) +{ + test.run(HEnv(hEnv), SQLFreeHandle(SQL_HANDLE_DBC, hDbc)); +} + +static void +freeConn(Test& test, SQLHANDLE hEnv, SQLHANDLE hDbc) +{ + test.run(HDbc(hDbc), SQLDisconnect(hDbc)); + test.run(HEnv(hEnv), SQLFreeHandle(SQL_HANDLE_DBC, hDbc)); +} + +static void +freeStmt(Test& test, SQLHANDLE hDbc, SQLHANDLE hStmt) +{ + test.run(HDbc(hDbc), SQLFreeHandle(SQL_HANDLE_STMT, hStmt)); +} + +static void +freeAll(Test& test, SQLHANDLE hEnv, SQLHANDLE hDbc, SQLHANDLE hStmt) +{ + freeStmt(test, hDbc, hStmt); + freeConn(test, hEnv, hDbc); + freeEnv(test, hEnv); +} + +static void +freeAll(Test& test, SQLHANDLE hEnv, SQLHANDLE hDbc, SQLHANDLE* hStmtList, unsigned nStmt) +{ + for (unsigned i = 0; i < nStmt; i++) + freeStmt(test, hDbc, hStmtList[i]); + freeConn(test, hEnv, hDbc); + freeEnv(test, hEnv); +} + +#define chkTuplesFetched(/*Test&*/ _test, /*SQLHANDLE*/ _hStmt, /*SQLUINTEGER*/ _countExp) \ +do { \ + SQLUINTEGER _count = (SQLUINTEGER)-1; \ + getTuplesFetched(_test, _hStmt, &_count); \ + test.chk(HStmt(_hStmt), _count == _countExp, "tuples: got %ld != %ld", (long)_count, (long)_countExp); \ +} while (0) + +static void +getTuplesFetched(Test& test, SQLHANDLE hStmt, SQLUINTEGER* count) +{ + *count = (SQLUINTEGER)-1; + test.run(HStmt(hStmt), SQLGetStmtAttr(hStmt, SQL_ATTR_NDB_TUPLES_FETCHED, count, SQL_IS_POINTER, 0)); +} + +static void +selectCount(Test& test, SQLHANDLE hStmt, const char* sql, long* count) +{ + if (opt.m_v >= 3) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + SQLINTEGER ind; + test.run(HStmt(hStmt), SQLBindCol(hStmt, 1, SQL_C_SLONG, count, 0, &ind)); + ind = -1; + *count = -1; + test.run(HStmt(hStmt), SQLExecute(hStmt)); + unsigned k = 0; + while (1) { + if (k == 1) + test.exp(SQL_NO_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + if (k == 1) + break; + k++; + } + test.chk(HStmt(hStmt), ind == sizeof(long), "got %d != %d", (int)ind, (int)sizeof(long)); + test.chk(HStmt(hStmt), *count >= 0, "got %ld < 0", *count); + chkTuplesFetched(test, hStmt, *count); +#ifndef iODBC + // + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); +#else + test.run(HStmt(hStmt), SQLFreeStmt(hStmt, SQL_CLOSE)); +#endif +} + +static void +selectCount(Test& test, SQLHANDLE hStmt, const Tab& tab, long* count) +{ + static char sql[MAX_SQL], *sqlptr; // XXX static or core + tab.selectCount(sqlptr = sql); + selectCount(test, hStmt, sql, count); +} + +static void +verifyCount(Test& test, SQLHANDLE hStmt, const Tab& tab, long countExp) +{ + long count = -1; + selectCount(test, hStmt, tab, &count); + test.chk(HStmt(hStmt), count == countExp, "got %ld != %ld", count, countExp); +} + +#define chkRowCount(/*Test&*/ _test, /*SQLHANDLE*/ _hStmt, /*SQLINTEGER*/ _countExp) \ +do { \ + SQLINTEGER _count = -1; \ + getRowCount(_test, _hStmt, &_count); \ + test.chk(HStmt(_hStmt), _count == _countExp, "rowcount: got %ld != %ld", (long)_count, (long)_countExp); \ +} while (0) + +static void +getRowCount(Test& test, SQLHANDLE hStmt, SQLINTEGER* count) +{ + *count = -1; + test.run(HStmt(hStmt), SQLRowCount(hStmt, count)); +} + +// handle allocation + +static void +testAlloc(Test& test) +{ + const unsigned n1 = (opt.m_scale >> 8) & 0xf; // default 500 = 0x1f4 + const unsigned n2 = (opt.m_scale >> 4) & 0xf; + const unsigned n3 = (opt.m_scale >> 0) & 0xf; + const unsigned count = n1 + n1 * n2 + n1 * n2 * n3; + SQLHANDLE hEnvList[0xf]; + SQLHANDLE hDbcList[0xf][0xf]; + SQLHANDLE hStmtList[0xf][0xf][0xf]; + for (unsigned i1 = 0; i1 < n1; i1++) { + SQLHANDLE& hEnv = hEnvList[i1]; + test.run(HNull, SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv)); + test.run(HEnv(hEnv), SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER)); + for (unsigned i2 = 0; i2 < n2; i2++) { + SQLHANDLE& hDbc = hDbcList[i1][i2]; + test.run(HEnv(hEnv), SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc)); +#ifdef unixODBC + test.exp(SQL_SUCCESS_WITH_INFO, "IM003", 0, false); // unicode?? + test.exp(SQL_SUCCESS_WITH_INFO, "01000", 0, false); // version?? +#endif + test.run(HDbc(hDbc), SQLConnect(hDbc, (SQLCHAR*)opt.m_dsn, SQL_NTS, (SQLCHAR*)"user", SQL_NTS, (SQLCHAR*)"pass", SQL_NTS)); + // some attributes + test.exp(SQL_ERROR, "HY092", -1, true); // read-only attribute + test.run(HDbc(hDbc), SQLSetConnectAttr(hDbc, SQL_ATTR_AUTO_IPD, (SQLPOINTER)SQL_TRUE, SQL_IS_UINTEGER)); + test.exp(SQL_ERROR, "HYC00", -1, true); // not supported + test.run(HDbc(hDbc), SQLSetConnectAttr(hDbc, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)SQL_TXN_SERIALIZABLE, SQL_IS_UINTEGER)); + test.run(HDbc(hDbc), SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG, (SQLPOINTER)"DEFAULT", strlen("DEFAULT"))); + for (unsigned i3 = 0; i3 < n3; i3++) { + SQLHANDLE& hStmt = hStmtList[i1][i2][i3]; + test.run(HDbc(hDbc), SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt)); + SQLHANDLE ipd0, ipd1; + SQLHANDLE ird0, ird1; + SQLHANDLE apd0, apd1, apd2; + SQLHANDLE ard0, ard1, ard2; + // get + ipd0 = ird0 = apd0 = ard0 = 0; + test.run(HStmt(hStmt), SQLGetStmtAttr(hStmt, SQL_ATTR_IMP_PARAM_DESC, &ipd0, SQL_IS_POINTER, 0)); + test.run(HStmt(hStmt), SQLGetStmtAttr(hStmt, SQL_ATTR_IMP_ROW_DESC, &ird0, SQL_IS_POINTER, 0)); + test.run(HStmt(hStmt), SQLGetStmtAttr(hStmt, SQL_ATTR_APP_PARAM_DESC, &apd0, SQL_IS_POINTER, 0)); + test.run(HStmt(hStmt), SQLGetStmtAttr(hStmt, SQL_ATTR_APP_ROW_DESC, &ard0, SQL_IS_POINTER, 0)); +#ifndef unixODBC + test.chk(HStmt(hStmt), ipd0 != 0, "got 0"); + test.chk(HStmt(hStmt), ird0 != 0, "got 0"); + test.chk(HStmt(hStmt), apd0 != 0, "got 0"); + test.chk(HStmt(hStmt), ard0 != 0, "got 0"); +#endif + // alloc + ipd1 = ird1 = apd1 = ard1 = 0; + test.run(HDbc(hDbc), SQLAllocHandle(SQL_HANDLE_DESC, hDbc, &ipd1)); + test.run(HDbc(hDbc), SQLAllocHandle(SQL_HANDLE_DESC, hDbc, &ird1)); + test.run(HDbc(hDbc), SQLAllocHandle(SQL_HANDLE_DESC, hDbc, &apd1)); + test.run(HDbc(hDbc), SQLAllocHandle(SQL_HANDLE_DESC, hDbc, &ard1)); + test.chk(HDbc(hDbc), ipd1 != 0 && ird1 != 0 && apd1 != 0 && ard1 != 0, "got null"); + // set + test.exp(SQL_ERROR, "HY092", -1, true); // read-only attribute + test.run(HStmt(hStmt), SQLSetStmtAttr(hStmt, SQL_ATTR_IMP_PARAM_DESC, ipd1, SQL_IS_POINTER)); + test.exp(SQL_ERROR, "HY092", -1, true); // read-only attribute + test.run(HStmt(hStmt), SQLSetStmtAttr(hStmt, SQL_ATTR_IMP_ROW_DESC, ird1, SQL_IS_POINTER)); + test.run(HStmt(hStmt), SQLSetStmtAttr(hStmt, SQL_ATTR_APP_PARAM_DESC, apd1, SQL_IS_POINTER)); + test.run(HStmt(hStmt), SQLSetStmtAttr(hStmt, SQL_ATTR_APP_ROW_DESC, ard1, SQL_IS_POINTER)); + // get + + apd2 = ard2 = 0; + test.run(HStmt(hStmt), SQLGetStmtAttr(hStmt, SQL_ATTR_APP_PARAM_DESC, &apd2, SQL_IS_POINTER, 0)); + test.run(HStmt(hStmt), SQLGetStmtAttr(hStmt, SQL_ATTR_APP_ROW_DESC, &ard2, SQL_IS_POINTER, 0)); + test.chk(HStmt(hStmt), apd2 == apd1, "got %x != %x", (unsigned)apd2, (unsigned)apd1); + test.chk(HStmt(hStmt), ard2 == ard1, "got %x != %x", (unsigned)ard2, (unsigned)ard1); + // free + test.run(HDbc(hDbc), SQLFreeHandle(SQL_HANDLE_DESC, ipd1)); + test.run(HDbc(hDbc), SQLFreeHandle(SQL_HANDLE_DESC, ird1)); + test.run(HDbc(hDbc), SQLFreeHandle(SQL_HANDLE_DESC, apd1)); + test.run(HDbc(hDbc), SQLFreeHandle(SQL_HANDLE_DESC, ard1)); + } + } + } + test.timerCnt(count); + if (opt.m_v >= 3) + ndbout << "allocated " << count << endl; + for (unsigned i1 = 0; i1 < n1; i1++) { + SQLHANDLE& hEnv = hEnvList[i1]; + for (unsigned i2 = 0; i2 < n2; i2++) { + SQLHANDLE& hDbc = hDbcList[i1][i2]; + if (i2 % 2 == 0) { + for (unsigned i3 = 0; i3 < n3; i3++) { + SQLHANDLE& hStmt = hStmtList[i1][i2][i3]; + test.run(HDbc(hDbc), SQLFreeHandle(SQL_HANDLE_STMT, hStmt)); + } + } else { + // cleaned up by SQLDisconnect + } + test.run(HDbc(hDbc), SQLDisconnect(hDbc)); + test.run(HEnv(hEnv), SQLFreeHandle(SQL_HANDLE_DBC, hDbc)); + } + test.run(HNull, SQLFreeHandle(SQL_HANDLE_ENV, hEnv)); + } + test.timerCnt(count); + if (opt.m_v >= 3) + ndbout << "freed " << count << endl; +} + +// create tables + +static void +testCreate(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmt; + allocAll(test, hEnv, hDbc, hStmt); + char sql[MAX_SQL], *sqlptr; + for (unsigned i = 0; i < tabCount; i++) { + Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + // drop + tab.drop(sqlptr = sql); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + test.exp(SQL_ERROR, "IM000", 2040709, false); + test.run(HStmt(hStmt), SQLExecute(hStmt)); + if (test.m_ret == SQL_SUCCESS) + test.chk(HStmt(hStmt), test.m_functionCode == SQL_DIAG_DROP_TABLE, "got %d != %d", test.m_functionCode, SQL_DIAG_DROP_TABLE); + if (test.m_ret == SQL_SUCCESS && opt.m_v >= 2) + ndbout << "table " << tab.m_name << " dropped" << endl; + if (test.m_ret != SQL_SUCCESS && opt.m_v >= 2) + ndbout << "table " << tab.m_name << " does not exist" << endl; + test.timerCnt(1); + // create + tab.create(sqlptr = sql); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + test.exp(SQL_ERROR, "IM000", 2040721, false); + test.run(HStmt(hStmt), SQLExecute(hStmt)); + if (test.m_ret == SQL_SUCCESS) + test.chk(HStmt(hStmt), test.m_functionCode == SQL_DIAG_CREATE_TABLE, "got %d != %d", test.m_functionCode, SQL_DIAG_CREATE_TABLE); + if (test.m_ret == SQL_SUCCESS && opt.m_v >= 2) + ndbout << "table " << tab.m_name << " created" << endl; + if (test.m_ret != SQL_SUCCESS && opt.m_v >= 2) + ndbout << "table " << tab.m_name << " already exists" << endl; + test.timerCnt(1); + } + freeAll(test, hEnv, hDbc, hStmt); +} + +// prepare without execute + +static void +testPrepare(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmt; + allocAll(test, hEnv, hDbc, hStmt); + char sql[MAX_SQL], *sqlptr; + for (unsigned cnt = opt.m_depth; cnt <= opt.m_depth; cnt++) { + for (unsigned i = 0; i < tabCount; i++) { + Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + tab.selectJoin(sqlptr = sql, cnt); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + SQLSMALLINT colCount = -1; + SQLSMALLINT colExp = cnt * tab.m_colCount; + test.run(HStmt(hStmt), SQLNumResultCols(hStmt, &colCount)); + test.chk(HStmt(hStmt), colCount == colExp, "got %d != %d", (int)colCount, (int)colExp); + test.timerCnt(1); + } + } + freeAll(test, hEnv, hDbc, hStmt); +} + +// catalog functions + +static void +testCatalog(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmt; + allocAll(test, hEnv, hDbc, hStmt); + odbc_typeinfo: { + long type[] = { + SQL_CHAR, SQL_VARCHAR, SQL_SMALLINT, SQL_INTEGER, SQL_BIGINT, SQL_REAL, SQL_DOUBLE + }; + unsigned rows[] = { + 1, 1, 2, 2, 2, 1, 1 // 2 for signed and unsigned + }; + for (unsigned i = 0; i < arraySize(type); i++) { + test.run(HStmt(hStmt), SQLGetTypeInfo(hStmt, type[i])); + long dataType = 0; + test.run(HStmt(hStmt), SQLBindCol(hStmt, 2, SQL_C_SLONG, &dataType, 0, 0)); + unsigned k = 0; + while (1) { + if (k == rows[i]) + test.exp(SQL_NO_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + if (k == rows[i]) + break; + test.chk(HStmt(hStmt), dataType == type[i], "got %ld != %ld", dataType, type[i]); + test.timerCnt(1); + k++; + } +#ifndef iODBC + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); +#else + freeStmt(test, hDbc, hStmt); + allocStmt(test, hDbc, hStmt); +#endif + } + if (opt.m_v >= 2) + ndbout << "found " << (UintPtr)arraySize(type) << " data types" << endl; + test.run(HStmt(hStmt), SQLFreeStmt(hStmt, SQL_UNBIND)); + } + odbc_tables: { + unsigned found[tabCount]; + for (unsigned i = 0; i < tabCount; i++) + found[i] = 0; + test.run(HStmt(hStmt), SQLTables(hStmt, (SQLCHAR*)0, 0, (SQLCHAR*)0, 0, (SQLCHAR*)0, 0, (SQLCHAR*)0, 0)); + char tableName[200] = ""; + char tableType[200] = ""; + test.run(HStmt(hStmt), SQLBindCol(hStmt, 3, SQL_C_CHAR, tableName, sizeof(tableName), 0)); + test.run(HStmt(hStmt), SQLBindCol(hStmt, 4, SQL_C_CHAR, tableType, sizeof(tableType), 0)); + unsigned cnt = 0; + while (1) { + test.exp(SQL_NO_DATA, 0, 0, false); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + if (test.m_ret == SQL_NO_DATA) + break; + test.timerCnt(1); + cnt++; + if (! blankeq(tableType, "TABLE")) + continue; + for (unsigned i = 0; i < tabCount; i++) { + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + if (! blankeq(tab.m_name, tableName)) + continue; + test.chk(HStmt(hStmt), found[i] == 0, "duplicate table %s", tab.m_name); + found[i]++; + } + } +#ifndef iODBC + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); +#else + freeStmt(test, hDbc, hStmt); + allocStmt(test, hDbc, hStmt); +#endif + for (unsigned i = 0; i < tabCount; i++) { + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + test.chk(HStmt(hStmt), found[i] == 1, "table %s not found", tab.m_name); + } + if (opt.m_v >= 2) + ndbout << "found " << cnt << " tables" << endl; + test.run(HStmt(hStmt), SQLFreeStmt(hStmt, SQL_UNBIND)); + } + odbc_columns: { + unsigned found[tabCount][maxColCount]; + for (unsigned i = 0; i < tabCount; i++) { + for (unsigned j = 0; j < maxColCount; j++) + found[i][j] = 0; + } + test.run(HStmt(hStmt), SQLColumns(hStmt, (SQLCHAR*)0, 0, (SQLCHAR*)0, 0, (SQLCHAR*)0, 0, (SQLCHAR*)0, 0)); + char tableName[200] = ""; + char columnName[200] = ""; + long dataType = 0; + test.run(HStmt(hStmt), SQLBindCol(hStmt, 3, SQL_C_CHAR, tableName, sizeof(tableName), 0)); + test.run(HStmt(hStmt), SQLBindCol(hStmt, 4, SQL_C_CHAR, columnName, sizeof(columnName), 0)); + test.run(HStmt(hStmt), SQLBindCol(hStmt, 5, SQL_C_SLONG, &dataType, 0, 0)); + unsigned cnt = 0; + while (1) { + test.exp(SQL_NO_DATA, 0, 0, false); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + if (test.m_ret == SQL_NO_DATA) + break; + test.timerCnt(1); + cnt++; + for (unsigned i = 0; i < tabCount; i++) { + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + if (! blankeq(tab.m_name, tableName)) + continue; + bool columnFound = false; + for (unsigned j = 0; j < tab.m_colCount; j++) { + const Col& col = tab.m_colList[j]; + if (! blankeq(col.m_name, columnName)) + continue; + test.chk(HStmt(hStmt), found[i][j] == 0, "duplicate column %s.%s", tableName, columnName); + found[i][j]++; + columnFound = true; + } + test.chk(HStmt(hStmt), columnFound, "unknown column %s.%s", tableName, columnName); + } + } +#ifndef iODBC + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); +#else + freeStmt(test, hDbc, hStmt); + allocStmt(test, hDbc, hStmt); +#endif + for (unsigned i = 0; i < tabCount; i++) { + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + for (unsigned j = 0; j < tab.m_colCount; j++) { + const Col& col = tab.m_colList[j]; + test.chk(HStmt(hStmt), found[i][j] == 1, "column %s.%s not found", tab.m_name, col.m_name); + } + } + if (opt.m_v >= 2) + ndbout << "found " << cnt << " columns" << endl; + test.run(HStmt(hStmt), SQLFreeStmt(hStmt, SQL_UNBIND)); + } + odbc_primarykeys: { + // table patterns are no allowed + for (unsigned i = 0; i < tabCount; i++) { + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + char tmp[200]; // p.i.t.a + strcpy(tmp, tab.m_name); + for (char* a = tmp; *a != 0; a++) { + if ('a' <= *a && *a <= 'z') + *a -= 'a' - 'A'; + } + test.run(HStmt(hStmt), SQLPrimaryKeys(hStmt, (SQLCHAR*)0, 0, (SQLCHAR*)0, 0, (SQLCHAR*)tmp, SQL_NTS)); + char tableName[200] = ""; + char columnName[200] = ""; + long keySeq = -1; + test.run(HStmt(hStmt), SQLBindCol(hStmt, 3, SQL_C_CHAR, tableName, sizeof(tableName), 0)); + test.run(HStmt(hStmt), SQLBindCol(hStmt, 4, SQL_C_CHAR, columnName, sizeof(columnName), 0)); + test.run(HStmt(hStmt), SQLBindCol(hStmt, 5, SQL_C_SLONG, &keySeq, 0, 0)); + unsigned cnt = 0; + while (1) { + if (cnt == tab.m_pkCount) + test.exp(SQL_NO_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + if (test.m_ret == SQL_NO_DATA) + break; + test.chk(HStmt(hStmt), keySeq == 1 + cnt, "got %ld != %u", keySeq, 1 + cnt); + const Col& col = tab.m_colList[tab.m_pkIndex[keySeq - 1]]; + test.chk(HStmt(hStmt), blankeq(columnName, col.m_name), "got %s != %s", columnName, col.m_name); + test.timerCnt(1); + cnt++; + } +#ifndef iODBC + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); +#else + freeStmt(test, hDbc, hStmt); + allocStmt(test, hDbc, hStmt); +#endif + } + test.run(HStmt(hStmt), SQLFreeStmt(hStmt, SQL_UNBIND)); + } + freeAll(test, hEnv, hDbc, hStmt); +} + +// insert + +static void +testInsert(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmtList[tabCount]; + allocAll(test, hEnv, hDbc, hStmtList, tabCount); + char sql[MAX_SQL], *sqlptr; + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + // prepare + tab.insertAll(sqlptr = sql); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + SQLSMALLINT parCount = -1; + test.run(HStmt(hStmt), SQLNumParams(hStmt, &parCount)); + test.chk(HStmt(hStmt), parCount == tab.m_colCount, "got %d != %d", (int)parCount, (int)tab.m_colCount); + // bind parameters + Row row(tab); + for (unsigned j = 0; j < tab.m_colCount; j++) { + Fld& fld = row.m_fldList[j]; + const Col& col = fld.m_col; + // every other at-exec + SQLPOINTER caddr; + SQLINTEGER* ind; + if (opt.m_noputd || j % 2 == 0) { + caddr = fld.caddr(); + ind = fld.ind(); + } else { + caddr = (SQLPOINTER)j; + ind = fld.need(); + } + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + j, SQL_PARAM_INPUT, col.ctype(), col.type(), col.size(), 0, caddr, col.csize(), ind)); + } + // bind columns (none) + SQLSMALLINT colCount = -1; + test.run(HStmt(hStmt), SQLNumResultCols(hStmt, &colCount)); + test.chk(HStmt(hStmt), colCount == 0, "got %d != 0", (int)colCount); + // execute + for (unsigned k = 0; k < opt.m_scale; k++) { + if (k % 5 == 0) { + // rebind + unsigned j = 0; + Fld& fld = row.m_fldList[j]; + const Col& col = fld.m_col; + // every other at-exec + SQLPOINTER caddr; + SQLINTEGER* ind; + if (opt.m_noputd || j % 2 == 0) { + caddr = fld.caddr(); + ind = fld.ind(); + } else { + caddr = (SQLPOINTER)j; + ind = fld.need(); + } + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + j, SQL_PARAM_INPUT, col.ctype(), col.type(), col.size(), 0, caddr, col.csize(), ind)); + } + row.calcPk(test, k); + row.calcNk(test); + unsigned needData = opt.m_noputd ? 0 : tab.m_colCount / 2; + if (needData) + test.exp(SQL_NEED_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLExecute(hStmt)); + test.chk(HStmt(hStmt), test.m_functionCode == SQL_DIAG_INSERT, "got %d != %d", test.m_functionCode, SQL_DIAG_INSERT); + if (needData) { + while (1) { + SQLPOINTER jPtr = (SQLPOINTER)999; + if (needData) + test.exp(SQL_NEED_DATA, 0, 0, true); + // completes SQLExecute on success + test.run(HStmt(hStmt), SQLParamData(hStmt, &jPtr)); + if (! needData) + break; + unsigned j = (unsigned)jPtr; + test.chk(HStmt(hStmt), j < tab.m_colCount && j % 2 != 0, "got %u 0x%x", j, j); + Fld& fld = row.m_fldList[j]; + const Col& col = fld.m_col; + SQLSMALLINT ctype = col.ctype(); + if (k % 2 == 0 || ctype != Col::CChar) + test.run(HStmt(hStmt), SQLPutData(hStmt, fld.caddr(), *fld.ind())); + else { + // put in pieces + unsigned size = col.csize() - 1; // omit null terminator + char* caddr = (char*)(fld.caddr()); + unsigned off = 0; + while (off < size) { + unsigned m = size / 7; // bytes to put + if (m == 0) + m = 1; + if (m > size - off) + m = size - off; + bool putNull = (*fld.ind() == SQL_NULL_DATA); + // no null terminator + SQLINTEGER len = putNull ? SQL_NULL_DATA : (int)m; + test.run(HStmt(hStmt), SQLPutData(hStmt, caddr + off, len)); + if (putNull) + break; + off += m; + } + } + needData--; + } + } + chkRowCount(test, hStmt, 1); + chkTuplesFetched(test, hStmt, 0); + } + test.timerCnt(opt.m_scale); + if (opt.m_v >= 3) + ndbout << "inserted " << opt.m_scale << " into " << tab.m_name << endl; + } + freeAll(test, hEnv, hDbc, hStmtList, tabCount); +} + +// count + +static void +testCount(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmtList[tabCount]; + allocAll(test, hEnv, hDbc, hStmtList, tabCount); + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + long count = -1; + selectCount(test, hStmt, tab, &count); + test.chk(HStmt(hStmt), count == opt.m_scale * opt.m_threads, "got %ld != %u", count, opt.m_scale * opt.m_threads); + test.timerCnt(count); + if (opt.m_v >= 3) + ndbout << "counted " << (int)count << " rows in " << tab.m_name << endl; + } + // scan all at same time + char sql[MAX_SQL], *sqlptr; + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + tab.selectAll(sqlptr = sql); + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + } + unsigned k = 0; + while (1) { + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + if (k == opt.m_scale * opt.m_threads) + test.exp(SQL_NO_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + if (k != opt.m_scale * opt.m_threads) { + chkTuplesFetched(test, hStmt, k + 1); + test.timerCnt(1); + } else { + chkTuplesFetched(test, hStmt, k); + test.exp(SQL_NO_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLMoreResults(hStmt)); + } + } + if (k == opt.m_scale * opt.m_threads) + break; + k++; + } + if (opt.m_v >= 3) + ndbout << "scanned " << opt.m_scale << " rows from each table" << endl; + freeAll(test, hEnv, hDbc, hStmtList, tabCount); +} + +// update + +static void +testUpdatePk(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmtList[tabCount]; + allocAll(test, hEnv, hDbc, hStmtList, tabCount); + char sql[MAX_SQL], *sqlptr; + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + // prepare + tab.updatePk(sqlptr = sql); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // bind parameters + Row row(tab); + SQLSMALLINT parCount = -1; + test.run(HStmt(hStmt), SQLNumParams(hStmt, &parCount)); + test.chk(HStmt(hStmt), parCount == tab.m_colCount, "got %d != %d", (int)parCount, (int)tab.m_colCount); + for (unsigned j = 0; j < tab.m_nkCount; j++) { + Fld& fld = row.m_fldList[tab.m_nkIndex[j]]; + const Col& col = fld.m_col; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + j, SQL_PARAM_INPUT, col.ctype(), col.type(), col.size(), 0, fld.caddr(), col.csize(), fld.ind())); + } + for (unsigned j = 0; j < tab.m_pkCount; j++) { + Fld& fld = row.m_fldList[tab.m_pkIndex[j]]; + const Col& col = fld.m_col; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + tab.m_nkCount + j, SQL_PARAM_INPUT, col.ctype(), col.type(), col.size(), 0, fld.caddr(), col.csize(), fld.ind())); + } + // bind columns (none) + SQLSMALLINT colCount = -1; + test.run(HStmt(hStmt), SQLNumResultCols(hStmt, &colCount)); + test.chk(HStmt(hStmt), colCount == 0, "got %d != 0", (int)colCount); + // execute + for (unsigned k = 0; k < opt.m_scale; k++) { + if (k % 5 == 0) { + unsigned j = 0; + Fld& fld = row.m_fldList[tab.m_nkIndex[j]]; + const Col& col = fld.m_col; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + j, SQL_PARAM_INPUT, col.ctype(), col.type(), col.size(), 0, fld.caddr(), col.csize(), fld.ind())); + } + row.calcPk(test, k); + row.calcNk(test); + test.run(HStmt(hStmt), SQLExecute(hStmt)); + test.chk(HStmt(hStmt), test.m_functionCode == SQL_DIAG_UPDATE_WHERE, "got %d != %d", test.m_functionCode, SQL_DIAG_UPDATE_WHERE); + chkRowCount(test, hStmt, 1); + // direct update, no read has been necessary + chkTuplesFetched(test, hStmt, 0); + } + test.timerCnt(opt.m_scale); + if (opt.m_v >= 3) + ndbout << "updated " << opt.m_scale << " in " << tab.m_name << endl; + } + freeAll(test, hEnv, hDbc, hStmtList, tabCount); +} + +static void +testUpdateScan(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmtList[tabCount]; + allocAll(test, hEnv, hDbc, hStmtList, tabCount); + char sql[MAX_SQL], *sqlptr; + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + // prepare + tab.updateRange(sqlptr = sql); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // bind parameters + Row row(tab); // for set clause + Row rowlo(tab); // for pk ranges + Row rowhi(tab); + SQLSMALLINT parCount = -1; + test.run(HStmt(hStmt), SQLNumParams(hStmt, &parCount)); + test.chk(HStmt(hStmt), parCount == tab.m_nkCount + 2 * tab.m_pkCount, "got %d != %d", (int)parCount, (int)tab.m_nkCount + 2 * (int)tab.m_pkCount); + for (unsigned j = 0; j < tab.m_nkCount; j++) { + const Col& col = tab.m_colList[tab.m_nkIndex[j]]; + Fld& fld = row.m_fldList[tab.m_nkIndex[j]]; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + j, SQL_PARAM_INPUT, col.ctype(), col.type(), col.size(), 0, fld.caddr(), col.csize(), fld.ind())); + } + bool canInterp = true; + for (unsigned j = 0; j < tab.m_pkCount; j++) { + const Col& col = tab.m_colList[tab.m_pkIndex[j]]; + Fld& fldlo = rowlo.m_fldList[tab.m_pkIndex[j]]; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + tab.m_nkCount + 2 * j + 0, SQL_PARAM_INPUT, col.ctype(), col.type(), col.size(), 0, fldlo.caddr(), col.csize(), fldlo.ind())); + Fld& fldhi = rowhi.m_fldList[tab.m_pkIndex[j]]; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + tab.m_nkCount + 2 * j + 1, SQL_PARAM_INPUT, col.ctype(), col.type(), col.size(), 0, fldhi.caddr(), col.csize(), fldhi.ind())); + if (col.m_type != Col::Char) + canInterp = false; // XXX no unsigned yet + } + // execute + row.calcPk(test, 0); + row.calcNk(test); + rowlo.calcPk(test, 0); + rowhi.calcPk(test, test.m_mul); // sucks + test.run(HStmt(hStmt), SQLExecute(hStmt)); + test.chk(HStmt(hStmt), test.m_functionCode == SQL_DIAG_UPDATE_WHERE, "got %d != %d", test.m_functionCode, SQL_DIAG_UPDATE_WHERE); + chkRowCount(test, hStmt, opt.m_scale); + chkTuplesFetched(test, hStmt, canInterp ? opt.m_scale : opt.m_scale * opt.m_threads); + test.timerCnt(opt.m_scale); + if (opt.m_v >= 3) + ndbout << "updated " << opt.m_scale << " in " << tab.m_name << endl; + } + freeAll(test, hEnv, hDbc, hStmtList, tabCount); +} + +// verify + +static void +testVerifyPk(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmtList[tabCount]; + allocAll(test, hEnv, hDbc, hStmtList, tabCount); + char sql[MAX_SQL], *sqlptr; + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + // prepare + tab.selectPk(sqlptr = sql); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // use same row for input and output + Row row(tab); + // bind parameters + SQLSMALLINT parCount = -1; + test.run(HStmt(hStmt), SQLNumParams(hStmt, &parCount)); + test.chk(HStmt(hStmt), parCount == tab.m_pkCount, "got %d != %d", (int)parCount, (int)tab.m_pkCount); + for (unsigned j = 0; j < tab.m_pkCount; j++) { + Fld& fld = row.m_fldList[tab.m_pkIndex[j]]; + const Col& col = fld.m_col; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + j, SQL_PARAM_INPUT, col.ctype(), col.type(), col.size(), 0, fld.caddr(), col.csize(), fld.ind())); + } + // bind columns + SQLSMALLINT colCount = -1; + test.run(HStmt(hStmt), SQLNumResultCols(hStmt, &colCount)); + test.chk(HStmt(hStmt), colCount == tab.m_colCount, "got %d != %d", (int)colCount, (int)tab.m_colCount); + for (unsigned j = 0; j < tab.m_colCount; j++) { + Fld& fld = row.m_fldList[j]; + const Col& col = fld.m_col; + test.run(HStmt(hStmt), SQLBindCol(hStmt, 1 + j, col.ctype(), fld.caddr(), col.csize(), fld.ind())); + } + // row for SQLGetData + Row rowGet(tab); + // reference row + Row rowRef(tab); + // execute + for (unsigned k = 0; k < opt.m_scale; k++) { + if (k % 5 == 0) { + // rebind + unsigned j = 0; + Fld& fld = row.m_fldList[tab.m_pkIndex[j]]; + const Col& col = fld.m_col; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + j, SQL_PARAM_INPUT, col.ctype(), col.type(), col.size(), 0, fld.caddr(), col.csize(), fld.ind())); + } + row.calcPk(test, k); + test.run(HStmt(hStmt), SQLExecute(hStmt)); + test.chk(HStmt(hStmt), test.m_functionCode == SQL_DIAG_SELECT_CURSOR, "got %d != %d", test.m_functionCode, SQL_DIAG_SELECT_CURSOR); + // fetch + for (unsigned k2 = 0; ; k2++) { + if (k2 == 1) + test.exp(SQL_NO_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + chkTuplesFetched(test, hStmt, 1); + if (k2 == 1) + break; + rowRef.calcPk(test, k); + test.chk(HStmt(hStmt), row.verifyPk(test, rowRef), "verify row=%d", k); + if (test.m_const) + rowRef.calcPk(test, 0); + rowRef.calcNk(test); + test.chk(HStmt(hStmt), row.verifyNk(test, rowRef), "verify row=%d", k); + // SQLGetData is supported independent of SQLBindCol + if (opt.m_nogetd) + continue; + for (unsigned j = 0; j < tab.m_colCount; j++) { + Fld& fld = rowGet.m_fldList[j]; + fld.zero(); + const Col& col = fld.m_col; + // test both variants + SQLSMALLINT ctype = k % 2 == 0 ? col.ctype() : SQL_ARD_TYPE; + if (ctype != Col::CChar) + test.run(HStmt(hStmt), SQLGetData(hStmt, 1 + j, ctype, fld.caddr(), col.csize(), fld.ind())); + else { + // get in pieces + unsigned size = col.csize() - 1; // omit null terminator + char* caddr = (char*)(fld.caddr()); + unsigned off = 0; + while (off < size) { + unsigned m = size / 3; // bytes to get + if (m == 0) + m = 1; + if (m > size - off) + m = size - off; + bool getNull = (rowRef.m_fldList[j].m_ind == SQL_NULL_DATA); + if (off + m < size && ! getNull) + test.exp(SQL_SUCCESS_WITH_INFO, "01004", -1, true); + // include null terminator in buffer size + test.run(HStmt(hStmt), SQLGetData(hStmt, 1 + j, ctype, caddr + off, m + 1, fld.ind())); + int ind = *fld.ind(); + if (getNull) { + test.chk(HStmt(hStmt), ind == SQL_NULL_DATA, "got %d", ind); + break; + } + test.chk(HStmt(hStmt), ind == size - off, "got %d != %u", ind, size - off); + off += m; + } + } + } + rowRef.calcPk(test, k); + test.chk(HStmt(hStmt), rowGet.verifyPk(test, rowRef), "verify row=%d", k); + if (test.m_const) + rowRef.calcPk(test, 0); + rowRef.calcNk(test); + test.chk(HStmt(hStmt), rowGet.verifyNk(test, rowRef), "verify row=%d", k); + // SQLGetData again + for (unsigned j = 0; j < tab.m_colCount; j++) { + Fld& fld = rowGet.m_fldList[j]; + const Col& col = fld.m_col; + // test both variants + SQLSMALLINT ctype = k % 2 == 0 ? col.ctype() : SQL_ARD_TYPE; + // expect no more data + test.exp(SQL_NO_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLGetData(hStmt, 1 + j, ctype, fld.caddr(), col.csize(), fld.ind())); + } + } + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); + } + test.timerCnt(opt.m_scale); + if (opt.m_v >= 3) + ndbout << "verified " << opt.m_scale << " from " << tab.m_name << endl; + } + freeAll(test, hEnv, hDbc, hStmtList, tabCount); +} + +static void +testVerifyScan(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmtList[tabCount]; + allocAll(test, hEnv, hDbc, hStmtList, tabCount); + char sql[MAX_SQL], *sqlptr; + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + // prepare + tab.selectRange(sqlptr = sql, ! opt.m_nosort); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // bind parameters + Row rowlo(tab); // use available PK fields.. + Row rowhi(tab); // since we have no other way for now + SQLSMALLINT parCount = -1; + test.run(HStmt(hStmt), SQLNumParams(hStmt, &parCount)); + test.chk(HStmt(hStmt), parCount == 2 * tab.m_pkCount, "got %d != %d", (int)parCount, 2 * (int)tab.m_pkCount); + for (unsigned j = 0; j < tab.m_pkCount; j++) { + const Col& col = tab.m_colList[tab.m_pkIndex[j]]; + Fld& fldlo = rowlo.m_fldList[tab.m_pkIndex[j]]; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + 2 * j + 0, SQL_PARAM_INPUT, col.ctype(), col.type(), col.size(), 0, fldlo.caddr(), col.csize(), fldlo.ind())); + Fld& fldhi = rowhi.m_fldList[tab.m_pkIndex[j]]; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + 2 * j + 1, SQL_PARAM_INPUT, col.ctype(), col.type(), col.size(), 0, fldhi.caddr(), col.csize(), fldhi.ind())); + } + // bind columns + Row row(tab); + SQLSMALLINT colCount = -1; + test.run(HStmt(hStmt), SQLNumResultCols(hStmt, &colCount)); + test.chk(HStmt(hStmt), colCount == tab.m_colCount, "got %d != %d", (int)colCount, (int)tab.m_colCount); + for (unsigned j = 0; j < tab.m_colCount; j++) { + Fld& fld = row.m_fldList[j]; + const Col& col = fld.m_col; + test.run(HStmt(hStmt), SQLBindCol(hStmt, 1 + j, col.ctype(), fld.caddr(), col.csize(), fld.ind())); + } + // execute + rowlo.calcPk(test, 0); + rowhi.calcPk(test, test.m_mul); // sucks + test.run(HStmt(hStmt), SQLExecute(hStmt)); + test.chk(HStmt(hStmt), test.m_functionCode == SQL_DIAG_SELECT_CURSOR, "got %d != %d", test.m_functionCode, SQL_DIAG_SELECT_CURSOR); + // reference row + Row rowRef(tab); + // fetch + unsigned k = 0; + SQLUINTEGER rowCount1 = (SQLUINTEGER)-1; + test.run(HStmt(hStmt), SQLSetStmtAttr(hStmt, SQL_ATTR_ROWS_FETCHED_PTR, &rowCount1, SQL_IS_POINTER)); + while (1) { + unsigned countExp; + if (k == opt.m_scale) { + countExp = k; + test.exp(SQL_NO_DATA, 0, 0, true); + } else { + countExp = k + 1; + } + test.run(HStmt(hStmt), SQLFetch(hStmt)); + // let me count the ways.. + chkRowCount(test, hStmt, countExp); + test.chk(HStmt(hStmt), rowCount1 == countExp, "got %lu != %u", rowCount1, countExp); + SQLUINTEGER rowCount2 = (SQLUINTEGER)-1; + test.run(HStmt(hStmt), SQLGetStmtAttr(hStmt, SQL_ATTR_ROW_NUMBER, &rowCount2, SQL_IS_POINTER, 0)); + test.chk(HStmt(hStmt), rowCount2 == countExp, "got %lu != %u", rowCount2, countExp); + if (k == opt.m_scale) + break; + if (! opt.m_nosort) { + // expecting k-th row + rowRef.calcPk(test, k); + test.chk(HStmt(hStmt), row.verifyPk(test, rowRef), "verify row=%d", k); + if (test.m_const) + rowRef.calcPk(test, 0); + rowRef.calcNk(test); + test.chk(HStmt(hStmt), row.verifyNk(test, rowRef), "verify row=%d", k); + } else { + // expecting random row + rowRef.copy(row); + test.chk(HStmt(hStmt), row.verifyNk(test, rowRef), "verify row=%d", k); + } + k++; + } + test.timerCnt(opt.m_scale); + if (opt.m_v >= 3) + ndbout << "verified " << opt.m_scale << " from " << tab.m_name << endl; + } + freeAll(test, hEnv, hDbc, hStmtList, tabCount); +} + +// self-join (scan followed by pk lookups) + +static void +testJoin(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmtList[tabCount]; + allocAll(test, hEnv, hDbc, hStmtList, tabCount); + char sql[MAX_SQL], *sqlptr; + for (unsigned cnt = opt.m_depth; cnt <= opt.m_depth; cnt++) { + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + tab.selectJoin(sqlptr = sql, cnt); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + } + unsigned k = 0; + while (1) { + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + if (k == opt.m_scale * opt.m_threads) + test.exp(SQL_NO_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + if (k == opt.m_scale * opt.m_threads) { + chkTuplesFetched(test, hStmt, k * opt.m_depth); + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); + } else { + chkTuplesFetched(test, hStmt, (k + 1) * opt.m_depth); + test.timerCnt(1); + } + } + if (k == opt.m_scale * opt.m_threads) + break; + k++; + } + } + freeAll(test, hEnv, hDbc, hStmtList, tabCount); +} + +// cartesian join (multiple nested scans) + +static void +testCart(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmtList[tabCount]; + allocAll(test, hEnv, hDbc, hStmtList, tabCount); + char sql[MAX_SQL], *sqlptr; + for (unsigned cnt = 2; cnt <= 2; cnt++) { + unsigned rows = 1; + //for (unsigned k = 0; k < opt.m_depth; k++) { + //rows *= opt.m_scale * opt.m_threads; + //} + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + tab.selectCart(sqlptr = sql, cnt); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + } + unsigned k = 0; + while (1) { + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + if (k == rows) + test.exp(SQL_NO_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + if (k == rows) { + //chkTuplesFetched(test, hStmt, k); + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); + } else { + //chkTuplesFetched(test, hStmt, k + 1); + test.timerCnt(1); + } + } + if (k == rows) + break; + k++; + } + } + freeAll(test, hEnv, hDbc, hStmtList, tabCount); +} + +// delete + +static void +testDeleteAll(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmtList[tabCount]; + allocAll(test, hEnv, hDbc, hStmtList, tabCount); + char sql[MAX_SQL], *sqlptr; + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + long count0 = -1; + selectCount(test, hStmt, tab, &count0); + tab.deleteAll(sqlptr = sql); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + if (count0 == 0) + test.exp(SQL_NO_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLExecute(hStmt)); +#ifndef iODBC + test.chk(HStmt(hStmt), test.m_functionCode == SQL_DIAG_DELETE_WHERE, "got %d != %d", test.m_functionCode, SQL_DIAG_DELETE_WHERE); +#endif + SQLINTEGER rowCount = -1; + getRowCount(test, hStmt, &rowCount); + test.timerCnt(rowCount); + test.chk(HStmt(hStmt), rowCount == count0, "got %d != %ld", (int)rowCount, count0); + chkTuplesFetched(test, hStmt, rowCount); + if (opt.m_v >= 3) + ndbout << "deleted " << (int)rowCount << " from " << tab.m_name << endl; + long count = -1; + selectCount(test, hStmt, tab, &count); + test.chk(HStmt(hStmt), count == 0, "got %ld != 0", count); + } + freeAll(test, hEnv, hDbc, hStmtList, tabCount); +} + +static void +testDeletePk(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmtList[tabCount]; + allocAll(test, hEnv, hDbc, hStmtList, tabCount); + char sql[MAX_SQL], *sqlptr; + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + // prepare + tab.deletePk(sqlptr = sql); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // bind parameters + Row row(tab); + SQLSMALLINT parCount = -1; + test.run(HStmt(hStmt), SQLNumParams(hStmt, &parCount)); + test.chk(HStmt(hStmt), parCount == tab.m_pkCount, "got %d != %d", (int)parCount, (int)tab.m_colCount); + for (unsigned j = 0; j < tab.m_pkCount; j++) { + Fld& fld = row.m_fldList[tab.m_pkIndex[j]]; + const Col& col = fld.m_col; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + j, SQL_PARAM_INPUT, col.ctype(), col.type(), col.size(), 0, fld.caddr(), col.csize(), fld.ind())); + } + // bind columns (none) + SQLSMALLINT colCount = -1; + test.run(HStmt(hStmt), SQLNumResultCols(hStmt, &colCount)); + test.chk(HStmt(hStmt), colCount == 0, "got %d != 0", (int)colCount); + // execute + for (unsigned k = 0; k < opt.m_scale; k++) { + row.calcPk(test, k); + test.run(HStmt(hStmt), SQLExecute(hStmt)); + test.chk(HStmt(hStmt), test.m_functionCode == SQL_DIAG_DELETE_WHERE, "got %d != %d", test.m_functionCode, SQL_DIAG_DELETE_WHERE); + chkRowCount(test, hStmt, 1); + // direct delete, no fetch required + chkTuplesFetched(test, hStmt, 0); + } + test.timerCnt(opt.m_scale); + if (opt.m_v >= 3) + ndbout << "updated " << opt.m_scale << " in " << tab.m_name << endl; + } + freeAll(test, hEnv, hDbc, hStmtList, tabCount); +} + +static void +testTrans(Test& test) +{ +#ifdef unixODBC + if (opt.m_v >= 1) + ndbout << "unixODBC does not support transactions - test skipped" << endl; +#else + SQLHANDLE hEnv, hDbc, hStmtList[tabCount]; + allocAll(test, hEnv, hDbc, hStmtList, tabCount); + char sql[MAX_SQL], *sqlptr; + // delete all + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + tab.deleteAll(sqlptr = sql); + test.exp(SQL_NO_DATA, 0, 0, false); + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + SQLINTEGER rowCount = -1; + getRowCount(test, hStmt, &rowCount); + if (opt.m_v >= 3) + ndbout << "deleted " << (int)rowCount << " from " << tab.m_name << endl; + } + setAutocommit(test, hDbc, false); + if (opt.m_v >= 2) + ndbout << "set autocommit OFF" << endl; + for (int commit = 0; commit < opt.m_scale; commit += 1) { + bool rollback = (commit % 2 == 0); + // XXX delete with no data leaves trans in error state for 2nd table + if (commit > 0 && rollback) { // previous case was commit + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + tab.deleteDirect(sqlptr = sql, 0); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.exp(SQL_NO_DATA, 0, 0, false); + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + } + test.run(HDbc(hDbc), SQLEndTran(SQL_HANDLE_DBC, hDbc, SQL_COMMIT)); + } + // insert + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + tab.insertDirect(sqlptr = sql, 0); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + if (opt.m_v >= 2) + ndbout << tab.m_name << ": inserted 1 row" << endl; + } + // count them via pk + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + tab.countDirect(sqlptr = sql, 0); + long count = -1; + long countExp = 1; + selectCount(test, hStmt, sql, &count); + test.chk(HStmt(hStmt), count == countExp, "got %ld != %ld", count, countExp); + } + // count them via scan + for (unsigned i = 0; i < tabCount; i++) { + // XXX hupp no work + break; + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + long count = -1; + long countExp = 1; + selectCount(test, hStmt, tab, &count); + test.chk(HStmt(hStmt), count == countExp, "got %ld != %ld", count, countExp); + } + // rollback or commit + if (rollback) { + if (opt.m_v >= 2) + ndbout << "end trans ROLLBACK" << endl; + test.run(HDbc(hDbc), SQLEndTran(SQL_HANDLE_DBC, hDbc, SQL_ROLLBACK)); + } else { + if (opt.m_v >= 2) + ndbout << "end trans COMMIT" << endl; + test.run(HDbc(hDbc), SQLEndTran(SQL_HANDLE_DBC, hDbc, SQL_COMMIT)); + } + // count them via pk again + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + tab.countDirect(sqlptr = sql, 0); + long count = -1; + long countExp = rollback ? 0 : 1; + selectCount(test, hStmt, sql, &count); + test.chk(HStmt(hStmt), count == countExp, "got %ld != %ld", count, countExp); + } + // count them via scan again + for (unsigned i = 0; i < tabCount; i++) { + // XXX hupp no work + break; + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + long count = -1; + long countExp = rollback ? 0 : 1; + selectCount(test, hStmt, tab, &count); + test.chk(HStmt(hStmt), count == countExp, "got %ld != %ld", count, countExp); + } + } + freeAll(test, hEnv, hDbc, hStmtList, tabCount); +#endif +} + +static void +testConcur(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmtList[tabCount]; + allocAll(test, hEnv, hDbc, hStmtList, tabCount); + char sql[MAX_SQL], *sqlptr; + for (unsigned i = 0; i < tabCount; i++) { + SQLHANDLE& hStmt = hStmtList[i]; + const Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + // delete all + tab.deleteAll(sqlptr = sql); + test.exp(SQL_NO_DATA, 0, 0, false); + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // insert some + unsigned rowcount = 10; + for (unsigned n = 0; n < rowcount; n++) { + tab.insertDirect(sqlptr = sql, n); + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + } + verifyCount(test, hStmt, tab, rowcount); + // start query scan followed by pk lookups + tab.selectJoin(sqlptr = sql, 2); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // start fetch + unsigned k = 0; + while (1) { + if (k > 0) + test.exp(SQL_ERROR, "24000", -1, true); // commit closed cursor + test.run(HStmt(hStmt), SQLFetch(hStmt)); + if (k > 0) + break; + // delete some random row + tab.deleteDirect(sqlptr = sql, k); + // try using same statement + test.exp(SQL_ERROR, "24000", -1, true); // cursor is open + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // try using different statement + SQLHANDLE hStmt2; + allocStmt(test, hDbc, hStmt2); + test.run(HStmt(hStmt2), SQLExecDirect(hStmt2, (SQLCHAR*)sql, SQL_NTS)); + k++; + } + test.exp(SQL_ERROR, "24000", -1, true); // cursor is not open + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); + test.timerCnt(rowcount); + } + freeAll(test, hEnv, hDbc, hStmtList, tabCount); +} + +static void +testReadcom(Test& test) +{ + testDeleteAll(test); + testInsert(test); + const unsigned nc = 3; + SQLHANDLE hEnv[nc], hDbc[nc], hStmt[nc]; + char sql[MAX_SQL], *sqlptr; + for (unsigned j = 0; j < nc; j++) + allocAll(test, hEnv[j], hDbc[j], hStmt[j]); + for (unsigned i = 0; i < tabCount; i++) { + Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + long count; + // check count + count = -1; + selectCount(test, hStmt[0], tab, &count); + test.chk(HStmt(hStmt[0]), count == opt.m_scale, "got %d != %d", (int)count, (int)opt.m_scale); + // scan delete uncommitted with handle 0 + setAutocommit(test, hDbc[0], false); + tab.deleteAll(sqlptr = sql); + if (opt.m_scale == 0) + test.exp(SQL_NO_DATA, 0, 0, false); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt[0]), SQLExecDirect(hStmt[0], (SQLCHAR*)sql, SQL_NTS)); + // scan via other tx should not hang and see all rows + for (unsigned j = 0; j < nc; j++) { + count = -1; + int want = j == 0 ? 0 : opt.m_scale; + selectCount(test, hStmt[j], tab, &count); + test.chk(HStmt(hStmt[j]), count == want, "tx %u: got %d != %d", j, (int)count, want); + if (opt.m_v >= 2) + ndbout << "tx " << j << " ok !" << endl; + } + // setting autocommit on commits the delete + setAutocommit(test, hDbc[0], true); + // check count + count = -1; + selectCount(test, hStmt[0], tab, &count); + test.chk(HStmt(hStmt[0]), count == 0, "got %d != 0", (int)count); + } + for (unsigned j = 0; j < nc; j++) + freeAll(test, hEnv[j], hDbc[j], hStmt[j]); +} + +static void +testPerf(Test& test) +{ + if (test.m_stuff == 0) { + SQLHANDLE hEnv, hDbc, hStmt; + allocAll(test, hEnv, hDbc, hStmt); + char sql[MAX_SQL], *sqlptr; + for (unsigned i = 0; i < tabCount; i++) { + Tab& tab = tabList[i]; + if (! tab.optok()) + continue; + test.exp(SQL_NO_DATA, 0, 0, false); + tab.deleteAll(sqlptr = sql); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + long count0 = -1; + // XXX triggers SEGV somewhere + //selectCount(test, hStmt, tab, &count0); + //test.chk(HStmt(hStmt), count0 == 0, "got %d != 0", (int)count0); + } + freeAll(test, hEnv, hDbc, hStmt); + return; + } + assert(test.m_stuff == 1 || test.m_stuff == 2); + bool ndbapi = (test.m_stuff == 1); + tt01: { + const unsigned OFF = 1000000; + const unsigned N = 25; + Tab& tab = tabList[1]; + if (! tab.optok()) + goto out; + if (ndbapi) { +#ifndef ndbODBC + if (opt.m_v >= 1) + ndbout << "running via DM - test skipped" << endl; +#else + Ndb* ndb = new Ndb("TEST_DB"); + ndb->init(); + if (ndb->waitUntilReady() != 0) { + ndbout << ndb->getNdbError() << endl; + fatal("waitUntilReady"); + } + Uint32 val[1+N]; + // insert + for (unsigned k = 1; k <= opt.m_scale; k++) { + NdbConnection* con = ndb->startTransaction(); + if (con == 0) { + ndbout << ndb->getNdbError() << endl; + fatal("startTransaction"); + } + NdbOperation* op = con->getNdbOperation(tab.m_upperName); + if (op == 0) { + ndbout << con->getNdbError() << endl; + fatal("getNdbOperation"); + } + if (op->insertTuple() == -1) { + ndbout << op->getNdbError() << endl; + fatal("insertTuple"); + } + for (unsigned j = 0; j <= N; j++) { + val[j] = (j == 0 ? k + test.m_no * OFF : k * j); + if (j == 0) { + if (op->equal(j, val[j]) == -1) { + ndbout << op->getNdbError() << endl; + fatal("equal"); + } + } else { + if (op->setValue(j, val[j]) == -1) { + ndbout << op->getNdbError() << endl; + fatal("setValue"); + } + } + } + if (con->execute(Commit) == -1) { + ndbout << con->getNdbError() << endl; + fatal("execute"); + } + ndb->closeTransaction(con); + } + test.timerCnt(opt.m_scale); + // select PK + for (unsigned k = 1; k <= opt.m_scale; k++) { + NdbConnection* con = ndb->startTransaction(); + if (con == 0) { + ndbout << ndb->getNdbError() << endl; + fatal("startTransaction"); + } + NdbOperation* op = con->getNdbOperation(tab.m_upperName); + if (op == 0) { + ndbout << con->getNdbError() << endl; + fatal("getNdbOperation"); + } + if (op->readTuple() == -1) { + ndbout << op->getNdbError() << endl; + fatal("insertTuple"); + } + for (unsigned j = 0; j <= N; j++) { + val[j] = (j == 0 ? k + test.m_no * OFF : 0); + if (j == 0) { + if (op->equal(j, val[j]) == -1) { + ndbout << op->getNdbError() << endl; + fatal("equal"); + } + } else { + if (op->getValue(j, (char*)&val[j]) == 0) { + ndbout << op->getNdbError() << endl; + fatal("getValue"); + } + } + } + if (con->execute(Commit) == -1) { + ndbout << con->getNdbError() << endl; + fatal("execute"); + } + for (unsigned j = 1; j <= N; j++) { + assert(val[j] == k * j); + } + ndb->closeTransaction(con); + } + test.timerCnt(opt.m_scale); + // delete PK + for (unsigned k = 1; k <= opt.m_scale; k++) { + NdbConnection* con = ndb->startTransaction(); + if (con == 0) { + ndbout << ndb->getNdbError() << endl; + fatal("startTransaction"); + } + NdbOperation* op = con->getNdbOperation(tab.m_upperName); + if (op == 0) { + ndbout << con->getNdbError() << endl; + fatal("getNdbOperation"); + } + if (op->deleteTuple() == -1) { + ndbout << op->getNdbError() << endl; + fatal("deleteTuple"); + } + unsigned j = 0; + val[j] = k + test.m_no * OFF; + if (op->equal(j, val[j]) == -1) { + ndbout << op->getNdbError() << endl; + fatal("equal"); + } + if (con->execute(Commit) == -1) { + ndbout << con->getNdbError() << endl; + fatal("execute"); + } + ndb->closeTransaction(con); + } + test.timerCnt(opt.m_scale); + delete ndb; +#endif + } else { + SQLHANDLE hEnv, hDbc, hStmt; + allocAll(test, hEnv, hDbc, hStmt); + long val[1+N]; + char sql[MAX_SQL], *sqlptr; + // insert + tab.insertAll(sqlptr = sql); + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + for (unsigned j = 0; j <= N; j++) { + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + j, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &val[j], 0, 0)); + } + test.m_perf = true; + for (unsigned k = 1; k <= opt.m_scale; k++) { + for (unsigned j = 0; j <= N; j++) { + val[j] = (j == 0 ? k + test.m_no * OFF : k * j); + } + test.run(HStmt(hStmt), SQLExecute(hStmt)); + } + test.m_perf = false; + test.timerCnt(opt.m_scale); + // select PK + tab.selectPk(sqlptr = sql); + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + for (unsigned j = 0; j <= N; j++) { + test.run(HStmt(hStmt), SQLBindCol(hStmt, 1 + j, SQL_C_SLONG, &val[j], 0, 0)); + } + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + N + 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &val[0], 0, 0)); + test.m_perf = true; + for (unsigned k = 1; k <= opt.m_scale; k++) { + val[0] = k + test.m_no * OFF; + test.run(HStmt(hStmt), SQLExecute(hStmt)); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + for (unsigned j = 1; j <= N; j++) { + assert(val[j] == k * j); + } + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); + } + test.m_perf = false; + test.timerCnt(opt.m_scale); + // delete PK + tab.deletePk(sqlptr = sql); + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + unsigned j = 0; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1 + j, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &val[j], 0, 0)); + test.m_perf = true; + for (unsigned k = 1; k <= opt.m_scale; k++) { + val[j] = k + test.m_no * OFF; + test.run(HStmt(hStmt), SQLExecute(hStmt)); + } + test.m_perf = false; + test.timerCnt(opt.m_scale); + freeAll(test, hEnv, hDbc, hStmt); + } + out: + ; + } +} + +struct Sql { + const char* m_sql; + int m_functionCode; + int m_rowCount; + int m_tuplesFetched; + long m_lastValue; + unsigned long m_bindValue; + int m_ret; + const char* m_state; + SQLINTEGER m_native; + bool m_reset; + // run this function instead + typedef void (*TestFunc)(Test& test); + TestFunc m_testFunc; + Sql() : + m_sql(0) { + } + Sql(const char* do_cmd) : + m_sql(do_cmd) { + } + Sql(const char* sql, int functionCode, int rowCount, int tuplesFetched, long lastValue, long bindValue) : + m_sql(sql), + m_functionCode(functionCode), + m_rowCount(rowCount), + m_tuplesFetched(tuplesFetched), + m_lastValue(lastValue), + m_bindValue(bindValue), + m_ret(SQL_SUCCESS), + m_state(0), + m_native(0), + m_reset(true), + m_testFunc(0) { + } + // the 4 numbers after SQL_DIAG... rowCount tuplesFetched lastValue bindValue + Sql(const char* sql, int functionCode, int rowCount, int tuplesFetched, long lastValue, long bindValue, int ret, const char* state, SQLINTEGER native, bool reset) : + m_sql(sql), + m_functionCode(functionCode), + m_rowCount(rowCount), + m_tuplesFetched(tuplesFetched), + m_lastValue(lastValue), + m_bindValue(bindValue), + m_ret(ret), + m_state(state), + m_native(native), + m_reset(reset), + m_testFunc(0) { + } + Sql(const char* text, TestFunc testFunc) : + m_sql(text), + m_testFunc(testFunc) { + } + static const char* set_autocommit_on() { + return "set autocommit on"; + } + static const char* set_autocommit_off() { + return "set autocommit off"; + } + static const char* do_commit() { + return "commit"; + } + static const char* do_rollback() { + return "rollback"; + } +}; + +// 90 + +static const Sql +miscSql90[] = { + Sql("select * from dual", + SQL_DIAG_SELECT_CURSOR, 1, 0, -1, -1), + Sql("drop table tt90a", + SQL_DIAG_DROP_TABLE, -1, 0, -1, -1, + SQL_ERROR, "IM000", 2040709, false), + Sql("create table tt90a (a int, b int, c int, primary key(b, c)) storage(large) logging", + SQL_DIAG_CREATE_TABLE, -1, 0, -1, -1), + Sql() +}; + +// 91 + +static const Sql +miscSql91[] = { + Sql("drop table tt91a", + SQL_DIAG_DROP_TABLE, -1, 0, -1, -1, + SQL_ERROR, "IM000", 2040709, false), + Sql("create table tt91a (a bigint unsigned primary key, b bigint unsigned not null, c varchar(10))", + SQL_DIAG_CREATE_TABLE, -1, 0, -1, -1), + Sql("insert into tt91a values (1, 111, 'aaa')", + SQL_DIAG_INSERT, 1, 0, -1, -1), + // fails + Sql("insert into tt91a values (2, null, 'ccc')", + SQL_DIAG_INSERT, -1, 0, -1, -1, + SQL_ERROR, "IM000", 2014203, true), + Sql("update tt91a set b = 222 where a = 2", + SQL_DIAG_UPDATE_WHERE, 0, 0, -1, -1, + SQL_NO_DATA, 0, 0, true), + // two more + Sql("insert into tt91a values (2, 222, 'ccc')", + SQL_DIAG_INSERT, 1, 0, -1, -1), + Sql("insert into tt91a values (3, 333, 'bbb')", + SQL_DIAG_INSERT, 1, 0, -1, -1), + // direct update + Sql("update tt91a set b = 112 where a = 1", + SQL_DIAG_UPDATE_WHERE, 1, 0, -1, -1), + Sql("update tt91a set b = 113 where a = 1 and b > 111", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, -1), + // update and delete with interpreted scan + Sql("update tt91a set b = 114 where b < 114", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, -1), + Sql("delete from tt91a where b < 115", + SQL_DIAG_DELETE_WHERE, 1, 1, -1, -1), + Sql("insert into tt91a values (1, 111, 'aaa')", + SQL_DIAG_INSERT, 1, 0, -1, -1), + // check rows: 1,111,aaa + 2,222,ccc + 3,333,bbb + Sql("select * from tt91a order by c", + SQL_DIAG_SELECT_CURSOR, 3, 3, 2, -1), + Sql("select * from tt91a order by c desc", + SQL_DIAG_SELECT_CURSOR, 3, 3, 1, -1), + Sql("select * from tt91a where a = 2", + SQL_DIAG_SELECT_CURSOR, 1, 1, -1, -1), + Sql("select * from tt91a where a + b = 224", + SQL_DIAG_SELECT_CURSOR, 1, 3, -1, -1), + Sql("select * from tt91a where a = 4", + SQL_DIAG_SELECT_CURSOR, 0, 0, -1, -1), + Sql("select b-a from tt91a order by a-b", + SQL_DIAG_SELECT_CURSOR, 3, 3, 110, -1), + Sql("select sum(a+b) from tt91a", + SQL_DIAG_SELECT_CURSOR, 1, 3, 672, -1), + Sql("select x.b, y.b, z.b from tt91a x, tt91a y, tt91a z where x.b <= y.b and y.b < z.b order by x.b", + SQL_DIAG_SELECT_CURSOR, 4, 13, 222, -1), + Sql("select x.b, y.b, z.b from tt91a x, tt91a y, tt91a z where x.b + y.b = z.b order by x.b", + SQL_DIAG_SELECT_CURSOR, 3, 15, 222, -1), + // tmp index + Sql("create unique hash index xx91a on tt91a(b)", + SQL_DIAG_CREATE_INDEX, -1, -1, -1, -1), + Sql("select x.b, y.b, z.b from tt91a x, tt91a y, tt91a z where x.b + y.b = z.b order by x.b", + SQL_DIAG_SELECT_CURSOR, 3, 15, 222, -1), + Sql("drop index xx91a on tt91a", + SQL_DIAG_DROP_INDEX, -1, -1, -1, -1), + // add some duplicates + Sql("insert into tt91a values (4, 222, 'ccc')", + SQL_DIAG_INSERT, 1, -1, -1, -1), + Sql("insert into tt91a values (5, 333, 'bbb')", + SQL_DIAG_INSERT, 1, -1, -1, -1), + Sql("insert into tt91a values (6, 333, 'bbb')", + SQL_DIAG_INSERT, 1, -1, -1, -1), + // check rows: 1,111,aaa + 2 * 2,222,ccc + 3 * 3,333,bbb + Sql("select count(*) from tt91a", + SQL_DIAG_SELECT_CURSOR, 1, -1, 6, -1), + Sql("select a+b from tt91a where (b = 111 or b = 222 ) and (b = 222 or b = 333) and a > 1 and a < 3", + SQL_DIAG_SELECT_CURSOR, 1, -1, 224, -1), + Sql("select sum(a) from tt91a having min(a) = 1 and max(a) = 6", + SQL_DIAG_SELECT_CURSOR, 1, -1, 21, -1), + Sql("select sum(a) from tt91a where a = 2 or a = 4 having min(a) = 2 and max(a) = 4", + SQL_DIAG_SELECT_CURSOR, 1, -1, 6, -1), + Sql("select sum(a) from tt91a having min(a) = 1 and max(a) = 5", + SQL_DIAG_SELECT_CURSOR, 0, -1, -1, -1), + Sql("select sum(a), b from tt91a group by b order by b", + SQL_DIAG_SELECT_CURSOR, 3, -1, 14, -1), + Sql("select sum(a), b, c from tt91a group by b, c order by c", + SQL_DIAG_SELECT_CURSOR, 3, -1, 6, -1), + Sql("select b, sum(a) from tt91a group by b having b = 37 * sum(a)", + SQL_DIAG_SELECT_CURSOR, 1, -1, 222, -1), + // simple varchar vs interpreter test + Sql("select count(*) from tt91a where c = 'ccc'", + SQL_DIAG_SELECT_CURSOR, 1, 2, 2, -1), + Sql("select count(*) from tt91a where c like '%b%'", + SQL_DIAG_SELECT_CURSOR, 1, 3, 3, -1), + // interpreter limits (crashes in api on v211) +#if NDB_VERSION_MAJOR >= 3 + Sql("select count(*) from tt91a where a in (99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2)", + SQL_DIAG_SELECT_CURSOR, 1, 5, 5, -1), + Sql("select count(*) from tt91a where c in ('xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','bbb','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy','zzzzz','xxxxx','yyyyy')", + SQL_DIAG_SELECT_CURSOR, 1, 3, 3, -1), +#endif + // distinct + Sql("select distinct b from tt91a order by b", + SQL_DIAG_SELECT_CURSOR, 3, -1, 333, -1), + // some illegal groupings + Sql("select a from tt91a group by b", + -1, -1, -1, -1, -1, + SQL_ERROR, "IM000", -1, -1), + Sql("select sum(a) from tt91a group by b having a = 2", + -1, -1, -1, -1, -1, + SQL_ERROR, "IM000", -1, -1), + Sql("select sum(a) from tt91a group by b order by a", + -1, -1, -1, -1, -1, + SQL_ERROR, "IM000", -1, -1), + // string functions + Sql("insert into tt91a (c, b, a) values ('abcdef', 999, 9)", + SQL_DIAG_INSERT, 1, -1, -1, -1), + Sql("select count(*) from tt91a where left(c, 2) = 'ab' and substr(c, 3, 2) = 'cd' and right(c, 2) = 'ef'", + SQL_DIAG_SELECT_CURSOR, 1, -1, 1, -1), + // nulls + Sql("update tt91a set c = null where a > 8", + SQL_DIAG_UPDATE_WHERE, 1, -1, -1, -1), + Sql("select a from tt91a where c is null and b is not null order by a", + SQL_DIAG_SELECT_CURSOR, 1, -1, 9, -1), + Sql("select a from tt91a where not (c is not null or b is null) order by a", + SQL_DIAG_SELECT_CURSOR, 1, -1, 9, -1), + // null value guard in interpreter + Sql("select count(*) from tt91a where c < 'x' or c > 'x' or c != 'x' or c = 'x'", + SQL_DIAG_SELECT_CURSOR, 1, 6, 6, -1), + Sql("delete from tt91a where c is null", + SQL_DIAG_DELETE_WHERE, 1, -1, -1, -1), + // indexes + Sql("update tt91a set b = a + 5", + SQL_DIAG_UPDATE_WHERE, 6, 6, -1, -1), + Sql("create unique hash index xx91a on tt91a(b)", + SQL_DIAG_CREATE_INDEX, -1, -1, -1, -1), + // scan y primary key x + Sql("select x.b from tt91a x, tt91a y where x.a = y.b + 0", + SQL_DIAG_SELECT_CURSOR, 1, 7, 11, -1), + // scan x index y + Sql("select x.b from tt91a x, tt91a y where x.a + 0 = y.b", + SQL_DIAG_SELECT_CURSOR, 1, -1, 11, -1), + // scan x scan y + Sql("select x.b from tt91a x, tt91a y where x.a + 0 = y.b + 0", + SQL_DIAG_SELECT_CURSOR, 1, -1, 11, -1), + // dml ops + Sql("delete from tt91a where b = 11 and a > 999", + SQL_DIAG_DELETE_WHERE, 0, 1, -1, -1, + SQL_NO_DATA, 0, 0, true), + Sql("delete from tt91a where b = 11", + SQL_DIAG_DELETE_WHERE, 1, 0, -1, -1), + Sql("delete from tt91a where b = 11", + SQL_DIAG_DELETE_WHERE, 0, 0, -1, -1, + SQL_NO_DATA, 0, 0, true), + Sql("update tt91a set b = 10*10 where b = 10", + SQL_DIAG_UPDATE_WHERE, 1, 0, -1, -1), + Sql("update tt91a set b = 10 where b = 10*10", + SQL_DIAG_UPDATE_WHERE, 1, 0, -1, -1), + Sql("update tt91a set b = 10*10 where b = 10 and b >= 10", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, -1), + Sql("update tt91a set b = 10 where b = 10*10 and b >= 10*10", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, -1), + // char vs varchar + Sql("drop table tt91b", + SQL_DIAG_DROP_TABLE, -1, -1, -1, -1, + SQL_ERROR, "IM000", 2040709, false), + Sql("create table tt91b (a int primary key, b char(5), c varchar(5))", + SQL_DIAG_CREATE_TABLE, -1, -1, -1, -1), + Sql("insert into tt91b values (1, 'abc', 'abc')", + SQL_DIAG_INSERT, 1, -1, -1, -1), + Sql("insert into tt91b values (2, 'xyz', 'xyz')", + SQL_DIAG_INSERT, 1, -1, -1, -1), + Sql("insert into tt91b values (3, 'xyz', 'xyz ')", + SQL_DIAG_INSERT, 1, -1, -1, -1), + // char = char strips blanks + Sql("select count(*) from tt91b x where (x.b = 'abc') or x.a = x.a+1", + SQL_DIAG_SELECT_CURSOR, 1, 3, 1, -1), + Sql("select count(*) from tt91b x where (x.b = 'abc')", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + Sql("select count(*) from tt91b x where (x.b = 'abc ') or x.a = x.a+1", + SQL_DIAG_SELECT_CURSOR, 1, 3, 1, -1), + Sql("select count(*) from tt91b x where (x.b = 'abc ')", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + // varchar = char + Sql("select count(*) from tt91b x where (x.c = 'abc') or x.a = x.a+1", + SQL_DIAG_SELECT_CURSOR, 1, 3, 1, -1), + Sql("select count(*) from tt91b x where (x.c = 'abc')", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + Sql("select count(*) from tt91b x where (x.c = 'abc ') or x.a = x.a+1", + SQL_DIAG_SELECT_CURSOR, 1, 3, 0, -1), + Sql("select count(*) from tt91b x where (x.c = 'abc ')", + SQL_DIAG_SELECT_CURSOR, 1, 0, 0, -1), + // char = varchar + Sql("select count(*) from tt91b x, tt91b y where (x.b = y.c) or x.a = x.a+1 or y.a = y.a+1", + SQL_DIAG_SELECT_CURSOR, 1, -1, 2, -1), + Sql("select count(*) from tt91b x, tt91b y where (x.b = y.c)", + SQL_DIAG_SELECT_CURSOR, 1, -1, 2, -1), + // varchar = varchar + Sql("select count(*) from tt91b x, tt91b y where (x.c = y.c) or x.a = x.a+1 or y.a = y.a+1", + SQL_DIAG_SELECT_CURSOR, 1, -1, 3, -1), + Sql("select count(*) from tt91b x, tt91b y where (x.c = y.c)", + SQL_DIAG_SELECT_CURSOR, 1, -1, 3, -1), + // less + Sql("select 10 * x.a + y.a from tt91b x, tt91b y where (x.b < y.b) or x.a = x.a+1 or y.a = y.a+1 order by x.a, y.a", + SQL_DIAG_SELECT_CURSOR, 2, -1, 13, -1), + Sql("select 10 * x.a + y.a from tt91b x, tt91b y where (x.b < y.b) order by x.a, y.a", + SQL_DIAG_SELECT_CURSOR, 2, -1, 13, -1), + Sql("select 10 * x.a + y.a from tt91b x, tt91b y where (x.c < y.c) or x.a = x.a+1 or y.a = y.a+1 order by x.a, y.a", + SQL_DIAG_SELECT_CURSOR, 3, -1, 23, -1), + Sql("select 10 * x.a + y.a from tt91b x, tt91b y where (x.c < y.c) order by x.a, y.a", + SQL_DIAG_SELECT_CURSOR, 3, -1, 23, -1), + // like + Sql("select count(*) from tt91b x where (x.b like 'a%') or x.a = x.a+1", + SQL_DIAG_SELECT_CURSOR, 1, 3, 1, -1), + Sql("select count(*) from tt91b x where (x.b like 'a%')", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + Sql("select count(*) from tt91b x where (x.b like 'x%z') or x.a = x.a+1", + SQL_DIAG_SELECT_CURSOR, 1, 3, 0, -1), + Sql("select count(*) from tt91b x where (x.b like 'x%z')", + SQL_DIAG_SELECT_CURSOR, 1, 0, 0, -1), + Sql("select count(*) from tt91b x where (x.a+0 = 2 and x.c like 'x%z') or x.a = x.a+1", + SQL_DIAG_SELECT_CURSOR, 1, 3, 1, -1), + Sql("select count(*) from tt91b x where (x.a+0 = 2 and x.c like 'x%z')", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + Sql("select count(*) from tt91b x where (x.a+0 = 3 and x.c like 'x%z ') or x.a = x.a+1", + SQL_DIAG_SELECT_CURSOR, 1, 3, 1, -1), + Sql("select count(*) from tt91b x where (x.a+0 = 3 and x.c like 'x%z ')", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + Sql() +}; + +// 92 + +static void +testMisc92a(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmt; + allocAll(test, hEnv, hDbc, hStmt); + char sql[MAX_SQL]; + char tname[20]; + sprintf(tname, "tt92%c", 0140 + test.m_no); + if (test.m_loop == 1) { + lock_mutex(); + sprintf(sql, "drop table %s", tname); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.exp(SQL_ERROR, "IM000", 2040709, false); + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + sprintf(sql, "create table %s (a int unsigned primary key, b int unsigned not null)", tname); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + unlock_mutex(); + } else { + sprintf(sql, "delete from %s", tname); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.exp(SQL_NO_DATA, 0, 0, false); + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + test.run(HStmt(hStmt), SQLEndTran(SQL_HANDLE_DBC, hDbc, SQL_COMMIT)); + } + for (int on = true; on >= false; on--) { + if (opt.m_v >= 2) + ndbout << "set autocommit " << (on ? "ON" : "OFF") << endl; + setAutocommit(test, hDbc, on); + // insert rows + if (opt.m_v >= 2) + ndbout << "SQL: insert into " << tname << " ..." << opt.m_scale << endl; + for (unsigned k = 0; k < opt.m_scale; k++) { + sprintf(sql, "insert into %s values (%u, %u)", tname, k, 10 * k); + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + } + // commit always + test.run(HStmt(hStmt), SQLEndTran(SQL_HANDLE_DBC, hDbc, SQL_COMMIT)); + // scan delete + sprintf(sql, "delete from %s", tname); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // rollback or commit + test.run(HStmt(hStmt), SQLEndTran(SQL_HANDLE_DBC, hDbc, on ? SQL_COMMIT : SQL_ROLLBACK)); + // count + long count = -1; + sprintf(sql, "select count(*) from %s", tname); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + selectCount(test, hStmt, sql, &count); + test.chk(HStmt(hStmt), count == on ? 0 : opt.m_scale, "%s: got %d != %d", tname, (int)count, (int)opt.m_scale); + } + freeAll(test, hEnv, hDbc, hStmt); +} + +static const Sql +miscSql92[] = { + // create in C func + Sql("testMisc92a", testMisc92a), + Sql() +}; + +// 93 + +static void +testMisc93a(Test& test) +{ + SQLHANDLE hEnv[2], hDbc[2], hStmt[2]; + allocAll(test, hEnv[0], hDbc[0], hStmt[0]); + allocAll(test, hEnv[1], hDbc[1], hStmt[1]); + char sql[MAX_SQL]; + // select via primary key + setAutocommit(test, hDbc[0], false); + sprintf(sql, "select c1 from tt93a where c0 = 1"); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt[0]), SQLExecDirect(hStmt[0], (SQLCHAR*)sql, SQL_NTS)); + // update via another trans must time out + sprintf(sql, "update tt93a set c1 = 'b' where c0 = 1"); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt[1]), SQLExecDirect(hStmt[1], (SQLCHAR*)sql, SQL_NTS)); + freeAll(test, hEnv[0], hDbc[0], hStmt[0]); + freeAll(test, hEnv[1], hDbc[1], hStmt[1]); +} + +static const Sql +miscSql93[] = { + // create in C func + Sql("drop table tt93a", + SQL_DIAG_DROP_TABLE, -1, 0, -1, -1, + SQL_ERROR, "IM000", 2040709, false), + Sql("create table tt93a (c0 int primary key, c1 char(10))", + SQL_DIAG_CREATE_TABLE, -1, 0, -1, -1), + Sql("insert into tt93a values(1, 'a')", + SQL_DIAG_INSERT, 1, 0, -1, -1), + Sql("testMisc93a", testMisc93a), + Sql() +}; + +// 95 + +static const Sql +miscSql95[] = { + Sql("drop table tt95a", + SQL_DIAG_DROP_TABLE, -1, 0, -1, -1, + SQL_ERROR, "IM000", 2040709, false), + Sql("create table tt95a (a int not null, b char(10) not null, c int not null, d char(10), primary key(a, b)) storage(small)", + SQL_DIAG_CREATE_TABLE, -1, 0, -1, -1), + // ordered index create and drop + Sql("create index xx95a on tt95a (c, d) nologging", + SQL_DIAG_CREATE_INDEX, -1, -1, -1, -1), + Sql("drop index xx95a on tt95a", + SQL_DIAG_DROP_INDEX, -1, -1, -1, -1), + Sql("create index xx95a on tt95a (c) nologging", + SQL_DIAG_CREATE_INDEX, -1, -1, -1, -1), + Sql("insert into tt95a values(1, 'a', 10, 'b')", + SQL_DIAG_INSERT, 1, -1, -1, -1), + Sql("insert into tt95a values(2, 'a', 20, 'b')", + SQL_DIAG_INSERT, 1, -1, -1, -1), + Sql("insert into tt95a values(3, 'a', 30, 'b')", + SQL_DIAG_INSERT, 1, -1, -1, -1), + Sql("select a from tt95a where c = 20", + SQL_DIAG_SELECT_CURSOR, 1, 1, 2, -1), + Sql("delete from tt95a where c = 10", + SQL_DIAG_DELETE_WHERE, 1, 1, -1, -1), + Sql("update tt95a set c = 300 where c = 30", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, -1), + Sql("delete from tt95a where c = 300", + SQL_DIAG_DELETE_WHERE, 1, 1, -1, -1), + Sql("delete from tt95a", + SQL_DIAG_DELETE_WHERE, 1, 1, -1, -1), + // simple insert and rollback + Sql("-- simple insert and rollback"), + Sql(Sql::set_autocommit_off()), + Sql("insert into tt95a values(1, 'a', 10, 'b')", + SQL_DIAG_INSERT, 1, -1, -1, -1), + Sql("select count(*) from tt95a", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + Sql("select count(*) from tt95a where c = 10", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + Sql(Sql::do_rollback()), + Sql(Sql::set_autocommit_on()), + Sql("select count(*) from tt95a", + SQL_DIAG_SELECT_CURSOR, 1, 0, 0, -1), + // simple update and rollback + Sql("-- simple update and rollback"), + Sql("insert into tt95a values(1, 'a', 10, 'b')", + SQL_DIAG_INSERT, 1, -1, -1, -1), + Sql(Sql::set_autocommit_off()), + Sql("update tt95a set c = 20 where c = 10", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, -1), + Sql("select count(*) from tt95a", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + Sql("select count(*) from tt95a where c = 20", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + Sql(Sql::do_rollback()), + Sql(Sql::set_autocommit_on()), + Sql("select count(*) from tt95a where c = 10", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + // simple delete and rollback + Sql("-- simple delete and rollback"), + Sql(Sql::set_autocommit_off()), + Sql("delete from tt95a where c = 10", + SQL_DIAG_DELETE_WHERE, 1, 1, -1, -1), + Sql("select count(*) from tt95a", + SQL_DIAG_SELECT_CURSOR, 0, 0, 0, -1), + Sql("select count(*) from tt95a where c = 10", + SQL_DIAG_SELECT_CURSOR, 0, 0, 0, -1), + Sql(Sql::do_rollback()), + Sql(Sql::set_autocommit_on()), + Sql("select count(*) from tt95a where c = 10", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + // multiple update + Sql("-- multiple update and rollback"), + Sql(Sql::set_autocommit_off()), + Sql("update tt95a set c = 20 where c = 10", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, -1), + Sql("select count(*) from tt95a where c = 20", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + Sql("update tt95a set c = 30 where c = 20", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, -1), + Sql("select count(*) from tt95a where c = 30", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + Sql("update tt95a set c = 40 where c = 30", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, -1), + Sql("select count(*) from tt95a where c = 40", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + Sql("update tt95a set c = 50 where c = 40", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, -1), + Sql("select count(*) from tt95a where c = 50", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + Sql(Sql::do_rollback()), + Sql(Sql::set_autocommit_on()), + Sql("select count(*) from tt95a where c = 10", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1, -1), + // another variant which found no tuple via index (aligment issue) + Sql("drop table tt95b", + SQL_DIAG_DROP_TABLE, -1, 0, -1, -1, + SQL_ERROR, "IM000", 2040709, false), + Sql("create table tt95b (a int primary key, b char(10) not null, c int not null)", + SQL_DIAG_CREATE_TABLE, -1, 0, -1, -1), + Sql("create index xx95b on tt95b (b, c) nologging", + SQL_DIAG_CREATE_INDEX, -1, -1, -1, -1), + Sql("insert into tt95b values(0,'0123456789',1)", + SQL_DIAG_INSERT, 1, -1, -1, -1), + Sql("select a from tt95b where b='0123456789'", + SQL_DIAG_SELECT_CURSOR, 1, 1, 0, -1), + // update index key to different value + Sql("update tt95b set b = '9876543210' where b = '0123456789'", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, -1), + // same value goes nuts... + Sql("update tt95b set b = '9876543210'", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, -1), +#if 0 + // ...if done via index key (variant of halloween problem) + Sql("update tt95b set b = '9876543210' where b = '9876543210'", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, -1), +#endif + Sql() +}; + +// 96 + +static void +testMisc96a(Test& test) +{ + // single thread + if (test.m_no != 1) + return; + SQLHANDLE hEnv, hDbc, hStmt; + allocAll(test, hEnv, hDbc, hStmt); + char sql[MAX_SQL], *sqlptr; + char tname[20]; + strcpy(tname, "tt96a"); + // drop table + scopy(sqlptr = sql, "drop table %s", tname); + test.exp(SQL_ERROR, "IM000", 2040709, false); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // create table with many attributes + unsigned attrs = 1 + opt.m_scale; + if (attrs > MAX_ATTRIBUTES_IN_TABLE) + attrs = MAX_ATTRIBUTES_IN_TABLE; + if (attrs > 64) + attrs = 64; + scopy(sqlptr = sql, "create table %s (c0 int primary key", tname); + for (unsigned j = 1; j < attrs; j++) { + if (j % 2 == 0) + scopy(sqlptr, ", c%d int unsigned not null", j); + else + scopy(sqlptr, ", c%d char(10) not null", j); + } + scopy(sqlptr, ")"); + if (opt.m_fragtype != 0) + scopy(sqlptr, " storage(%s)", opt.m_fragtype); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // create or drop indexes + const unsigned seed = 1000037 * test.m_loop + 1000039 * opt.m_scale; + srandom(seed); + const unsigned imax = opt.m_scale < 20 ? opt.m_scale : 20; + AttributeMask* imasks = new AttributeMask[imax]; + unsigned ccnt = 0; + unsigned dcnt = 0; + for (unsigned n = 0; n < imax; n++) + imasks[n].clear(); + while (ccnt + dcnt < opt.m_scale) { + char iname[20]; + unsigned n = urandom(imax); + sprintf(iname, "xx96a%02d", n); + AttributeMask& imask = imasks[n]; + unsigned sel = urandom(10); + if (imask.isclear()) { + // create one + unsigned ncol = 0; + unsigned cols[MAX_ATTRIBUTES_IN_INDEX]; + unsigned cnum = urandom(attrs); + cols[ncol++] = cnum; + while (ncol < MAX_ATTRIBUTES_IN_INDEX) { + unsigned sel2 = urandom(10); + if (sel2 < 2) + break; + unsigned cnum2 = urandom(attrs); + if (sel2 < 9 && cnum2 == 0) + continue; + unsigned j; + for (j = 0; j < ncol; j++) { + if (cols[j] == cnum2) + break; + } + if (j == ncol) + cols[ncol++] = cnum2; + } + if (sel < 3) { + scopy(sqlptr = sql, "create unique hash index %s on %s (", iname, tname); + for (unsigned j = 0; j < ncol; j++) + scopy(sqlptr, "%sc%d", j == 0 ? "" : ", ", cols[j]); + scopy(sqlptr, ")"); + } else { + scopy(sqlptr = sql, "create index %s on %s (", iname, tname); + for (unsigned j = 0; j < ncol; j++) + scopy(sqlptr, "%sc%d", j == 0 ? "" : ", ", cols[j]); + scopy(sqlptr, ") nologging"); + } + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + for (unsigned j = 0; j < ncol; j++) + imask.set(cols[j]); + ccnt++; + } else if (sel < 5 && ccnt > dcnt + 1) { + scopy(sqlptr = sql, "drop index %s on %s", iname, tname); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + imask.clear(); + dcnt++; + } + } + // insert unique data + unsigned rows = opt.m_scale; + unsigned* uval = new unsigned[rows]; + for (unsigned i = 0; i < rows; i++) { + uval[i] = urandom(4); + scopy(sqlptr = sql, "insert into %s values(", tname); + for (unsigned j = 0; j < attrs; j++) { + if (j != 0) + scopy(sqlptr, ","); + unsigned v = (i << 10) | (j << 2) | uval[i]; + if (j == 0) + scopy(sqlptr, "%u", i); + else if (j % 2 == 0) + scopy(sqlptr, "%u", v); + else + scopy(sqlptr, "'%010u'", v); + } + scopy(sqlptr, ")"); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + } + // update each row via random index + for (unsigned i = 0; i < rows; i++) { + unsigned uold = uval[i]; + uval[i] = 3 - uval[i]; + AttributeMask imask; + do { + unsigned j = urandom(imax); + imask = imasks[j]; + } while (imask.isclear()); + scopy(sqlptr = sql, "update %s set", tname); + for (unsigned j = 1; j < attrs; j++) { + if (j != 1) + scopy(sqlptr, ","); + /* + * Equality update is just barely doable before savepoints + * provided we change value of keys in every index. + */ + unsigned v = (i << 10) | (j << 2) | uval[i]; + if (j == 0) + ; + else if (j % 2 == 0) + scopy(sqlptr, " c%d=%u", j, v); + else + scopy(sqlptr, " c%d='%010u'", j, v); + } + scopy(sqlptr, " where 1=1"); + while (! imask.isclear()) { + unsigned j = urandom(attrs); + if (imask.get(j)) { + unsigned v = (i << 10) | (j << 2) | uold; + scopy(sqlptr, " and c%d=", j); + if (j == 0) + scopy(sqlptr, "%u", i); + else if (j % 2 == 0) + scopy(sqlptr, "%u", v); + else + scopy(sqlptr, "'%010u'", v); + imask.clear(j); + } + } + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + chkRowCount(test, hStmt, 1); + } + // delete all + scopy(sqlptr = sql, "delete from %s", tname); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // + if (opt.m_v >= 2) + ndbout << tname << ": creates " << ccnt << " drops " << dcnt << endl; + delete [] imasks; + delete [] uval; + freeAll(test, hEnv, hDbc, hStmt); +} + +static const Sql +miscSql96[] = { + Sql("testMisc96a", testMisc96a), + Sql() +}; + +// 97 + +static void +testMisc97a(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmt; + allocAll(test, hEnv, hDbc, hStmt); + const char* tname = "TT97A"; + const char* iname = "XX97A"; + char sql[MAX_SQL]; + // create in some thread + lock_mutex(); + if (my_sema == 0) { + if (opt.m_v >= 1) + ndbout << "thread " << test.m_no << " does setup" << endl; + sprintf(sql, "drop table %s", tname); + if (opt.m_v >= 2) + ndbout << "SQL[" << test.m_no << "]: " << sql << endl; + test.exp(SQL_ERROR, "IM000", 2040709, false); + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // a-pk b-index c-counter + sprintf(sql, "create table %s (a int primary key, b int, c int) storage(small)", tname); + if (opt.m_v >= 2) + ndbout << "SQL[" << test.m_no << "]: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + for (unsigned i = 0; i < opt.m_scale; i++) { + sprintf(sql, "insert into %s values (%d, %d, %d)", tname, i, 10 * i, 0); + if (opt.m_v >= 3) + ndbout << "SQL[" << test.m_no << "]: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + } + sprintf(sql, "create index %s on %s (b) nologging", iname, tname); + if (opt.m_v >= 2) + ndbout << "SQL[" << test.m_no << "]: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + my_sema = 1; + } + unlock_mutex(); + assert(my_sema == 1); + // parallel run - default rotating pk, ts, is + // frob: low 3 hex digits give alt sequence e.g. 0x311 = pk, pk, is + // frob: 4-th hex digit non-zero says use NDB API e.g. 0x1000 + unsigned typelist[3] = { 1, 2, 3 }; + for (unsigned i = 0; i < 3; i++) { + unsigned t = (opt.m_frob >> (i * 4)) & 0xf; + if (t != 0) + typelist[i] = t; + } + unsigned type = typelist[(test.m_no - 1) % 3]; + if ((opt.m_frob & 0xf000) == 0) { + for (unsigned i = 0; i < opt.m_scale; i++) { + if (type == 1) { + // pk update + sprintf(sql, "update %s set c = c + 1 where a = %d", tname, i % opt.m_scale); + if (opt.m_v >= 3) + ndbout << lock << "SQL[" << test.m_no << "]: " << sql << endl << unlock; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + } + if (type == 2) { + // table scan update + sprintf(sql, "update %s set c = c + 1 where b + 0 = %d", tname, 10 * i); + if (opt.m_v >= 3) + ndbout << lock << "SQL[" << test.m_no << "]: " << sql << endl << unlock; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + } + if (type == 3) { + // index scan update + sprintf(sql, "update %s set c = c + 1 where b = %d", tname, 10 * i); + if (opt.m_v >= 3) + ndbout << lock << "SQL[" << test.m_no << "]: " << sql << endl << unlock; + test.exp(SQL_NO_DATA, 0, 0, false); + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + } + } + } else { +#ifdef ndbODBC +#define CHK(o, x) do { if (! (x)) { fatal("line %d: %d %s", __LINE__, o->getNdbError().code, o->getNdbError().message); } } while (0) + Ndb* ndb = new Ndb("TEST_DB"); + ndb->init(); + CHK(ndb, ndb->waitUntilReady() == 0); + Int32 a, b, c; + for (unsigned i = 0; i < opt.m_scale; i++) { + if (type == 1) { + // pk update with exclusive read + NdbConnection* con; + NdbOperation* op; + CHK(ndb, (con = ndb->startTransaction()) != 0); + a = i; + c = -1; + CHK(con, (op = con->getNdbOperation(tname)) != 0); + CHK(op, op->readTupleExclusive() == 0); + CHK(op, op->equal((unsigned)0, (char*)&a, 0) == 0); + CHK(op, op->getValue(2, (char*)&c) != 0); + CHK(con, con->execute(NoCommit) == 0); + c = c + 1; + CHK(con, (op = con->getNdbOperation(tname)) != 0); + CHK(op, op->updateTuple() == 0); + CHK(op, op->equal((unsigned)0, (char*)&a, 0) == 0); + CHK(op, op->setValue(2, (char*)&c) == 0); + CHK(con, con->execute(Commit) == 0); + ndb->closeTransaction(con); + if (opt.m_v >= 3) + ndbout << lock << "thr " << test.m_no << " pk a=" << i << " c=" << c << endl << unlock; + } + if (type == 2) { + // table scan update + NdbConnection* con; + NdbOperation* op; + CHK(ndb, (con = ndb->startTransaction()) != 0); + CHK(con, (op = con->getNdbOperation(tname)) != 0); + CHK(con, op->openScanExclusive(240) == 0); + CHK(op, op->getValue((unsigned)0, (char*)&a) != 0); + CHK(op, op->getValue(2, (char*)&c) != 0); + CHK(con, con->executeScan() == 0); + unsigned rows = 0; + unsigned updates = 0; + while (1) { + int ret; + a = -1; + c = -1; + CHK(con, (ret = con->nextScanResult()) == 0 || ret == 1); + if (ret == 1) + break; + rows++; + if (a == i) { + NdbConnection* con2; + NdbOperation* op2; + CHK(ndb, (con2 = ndb->startTransaction()) != 0); + CHK(op, (op2 = op->takeOverForUpdate(con2)) != 0); + c = c + 1; + CHK(op2, op2->setValue(2, (char*)&c) == 0); + CHK(con2, con2->execute(Commit) == 0); + ndb->closeTransaction(con2); + updates++; + if (opt.m_v >= 3) + ndbout << lock << "thr " << test.m_no << " ts rows=" << rows << " a=" << i << " c=" << c << endl << unlock; + // test stop scan too + CHK(con, con->stopScan() == 0); + break; + } + } + ndb->closeTransaction(con); + test.chk(HStmt(hStmt), updates == 1, "got %u != 1", updates); + } + if (type == 3) { + // index scan update + NdbConnection* con; + NdbOperation* op; + CHK(ndb, (con = ndb->startTransaction()) != 0); + CHK(con, (op = con->getNdbOperation(iname, tname)) != 0); + CHK(con, op->openScanExclusive(240) == 0); + b = 10 * i; + CHK(con, op->setBound((unsigned)0, 4, &b, sizeof(b)) == 0); + CHK(op, op->getValue((unsigned)0, (char*)&a) != 0); + CHK(op, op->getValue(2, (char*)&c) != 0); + CHK(con, con->executeScan() == 0); + unsigned rows = 0; + unsigned updates = 0; + while (1) { + int ret; + a = -1; + c = -1; + CHK(con, (ret = con->nextScanResult()) == 0 || ret == 1); + if (ret == 1) + break; + rows++; + if (a == i) { + NdbConnection* con2; + NdbOperation* op2; + CHK(ndb, (con2 = ndb->startTransaction()) != 0); + CHK(op, (op2 = op->takeOverForUpdate(con2)) != 0); + c = c + 1; + CHK(op2, op2->setValue(2, (char*)&c) == 0); + CHK(con2, con2->execute(Commit) == 0); + ndb->closeTransaction(con2); + updates++; + if (opt.m_v >= 3) + ndbout << lock << "thr " << test.m_no << " is rows=" << rows << " a=" << i << " c=" << c << endl << unlock; + // test stop scan too + CHK(con, con->stopScan() == 0); + break; + } + } + ndb->closeTransaction(con); + test.chk(HStmt(hStmt), rows == 1, "got %u != 1", rows); + test.chk(HStmt(hStmt), updates == 1, "got %u != 1", updates); + } + } + delete ndb; +#undef CHK +#endif + } + // verify result + lock_mutex(); + if (++my_sema == 1 + opt.m_threads) { + if (opt.m_v >= 1) + ndbout << "thread " << test.m_no << " does verification" << endl; + sprintf(sql, "select * from %s order by a", tname); + if (opt.m_v >= 2) + ndbout << "SQL[" << test.m_no << "]: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + long a, b, c; + test.run(HStmt(hStmt), SQLBindCol(hStmt, 1, SQL_C_SLONG, &a, 0, 0)); + test.run(HStmt(hStmt), SQLBindCol(hStmt, 2, SQL_C_SLONG, &b, 0, 0)); + test.run(HStmt(hStmt), SQLBindCol(hStmt, 3, SQL_C_SLONG, &c, 0, 0)); + for (unsigned i = 0; i < opt.m_scale; i++) { + a = b = c = -1; + test.run(HStmt(hStmt), SQLFetch(hStmt)); + test.chk(HStmt(hStmt), a == i, "a: got %ld != %u", a, i); + test.chk(HStmt(hStmt), b == 10 * i, "b: got %ld != %u", b, 10 * i); + test.chk(HStmt(hStmt), c == opt.m_threads, "c: got %ld != %u", c, opt.m_threads); + if (opt.m_v >= 4) + ndbout << "verified " << i << endl; + } + test.exp(SQL_NO_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + if (opt.m_v >= 2) + ndbout << "thr " << test.m_no << " verified " << opt.m_scale << " rows" << endl; + my_sema = 0; + } + unlock_mutex(); + freeAll(test, hEnv, hDbc, hStmt); +} + +static const Sql +miscSql97[] = { + Sql("testMisc97a", testMisc97a), + Sql() +}; + +// 99 + +static void +testMisc99a(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmt; + allocAll(test, hEnv, hDbc, hStmt); + // bad + const char* sqlInsertBad = "insert into tt99a values(?, ?, ?, ?, ?)"; + test.exp(SQL_ERROR, "21S01", -1, true); + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sqlInsertBad, SQL_NTS)); + // good + const char* sqlInsert = "insert into tt99a (col1, col2, col3, col4, col5) values(?, ?, ?, ?, ?)"; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sqlInsert, SQL_NTS)); + unsigned long value; + for (unsigned i = 1; i <= 5; i++) { + test.run(HStmt(hStmt), SQLBindParameter(hStmt, i, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &value, 0, 0)); + } + const unsigned long base = 1000000000; + const unsigned long scale = 10; + for (value = base; value < base + scale; value++) { + test.run(HStmt(hStmt), SQLExecute(hStmt)); + } + // bug1: re-analyze of converted expression... + const char* sqlSelect = "select col5 from tt99a where col2 + 0 = ?"; + unsigned long output; + test.run(HStmt(hStmt), SQLBindCol(hStmt, 1, SQL_C_ULONG, &output, 0, 0)); + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sqlSelect, SQL_NTS)); + // bug2: previous bind must survive a new SQLPrepare + if (0) { + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &value, 0, 0)); + } + for (value = base; value < base + scale; value++) { + if (value > base + 4) { + // bug1: ...when IPD changed by JDBC + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &value, 0, 0)); + } + test.run(HStmt(hStmt), SQLExecute(hStmt)); + output = (unsigned long)-1; + test.run(HStmt(hStmt), SQLFetch(hStmt)); + test.chk(HStmt(hStmt), output == value, "got %lu != %lu", output, value); + test.exp(SQL_NO_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); + test.timerCnt(1); + } + freeAll(test, hEnv, hDbc, hStmt); +} + +static void +testMisc99c(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmt; + allocAll(test, hEnv, hDbc, hStmt); + const char* sql = "select b from tt99c where a = ?"; + const unsigned long c1 = 2100000000U; + const unsigned long c2 = 4100000000U; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + unsigned long aval, bval; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &aval, 0, 0)); + test.run(HStmt(hStmt), SQLBindCol(hStmt, 1, SQL_C_ULONG, &bval, 0, 0)); + // uno + for (unsigned i = 0; i < opt.m_scale; i++) { + aval = c1; + bval = (unsigned long)-1; + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << " [?=" << (Uint64)aval << "]" << endl; + test.run(HStmt(hStmt), SQLExecute(hStmt)); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + test.chk(HStmt(hStmt), bval == c2, "got %lu != %lu", bval, c2); + //test.exp(SQL_NO_DATA, 0, 0, true); + //test.run(HStmt(hStmt), SQLFetch(hStmt)); + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); + } + // dos + for (unsigned i = 0; i < opt.m_scale; i++) { + break; // XXX not yet, hangs in NDB ?!? + aval = c2; + bval = (unsigned long)-1; + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << " [?=" << (Uint64)aval << "]" << endl; + test.run(HStmt(hStmt), SQLExecute(hStmt)); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + test.chk(HStmt(hStmt), bval == c1, "got %lu != %lu", bval, c2); + //test.exp(SQL_NO_DATA, 0, 0, true); + //test.run(HStmt(hStmt), SQLFetch(hStmt)); + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); + } + freeAll(test, hEnv, hDbc, hStmt); +} + +static void +testMisc99d(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmt; + allocAll(test, hEnv, hDbc, hStmt); + const char* tname = "TT99D"; + char sql[MAX_SQL]; + sprintf(sql, "drop table %s", tname); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.exp(SQL_ERROR, "IM000", 2040709, false); + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + sprintf(sql, "create table %s (a bigint unsigned, b bigint, primary key (a))", tname); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLExecDirect(hStmt, (SQLCHAR*)sql, SQL_NTS)); + sprintf(sql, "insert into %s values (?, ?)", tname); + if (opt.m_v >= 2) + ndbout << "SQL: " << sql << endl; + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + // XXX replace by 100 when signed vs unsigned resolved + const unsigned num = 78; + SQLUBIGINT aval; + SQLBIGINT bval; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_UBIGINT, SQL_BIGINT, 0, 0, &aval, 0, 0)); + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 2, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, 0, 0, &bval, 0, 0)); + for (SQLBIGINT i = 0; i < num; i++) { + if (opt.m_v >= 3) + ndbout << "insert " << i << endl; + aval = i * i * i * i * i * i * i * i * i * i; // 10 + bval = -aval; + test.run(HStmt(hStmt), SQLExecute(hStmt)); + } + sprintf(sql, "select a, b from tt99d where a = ?"); + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql, SQL_NTS)); + SQLUBIGINT kval; + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_UBIGINT, SQL_BIGINT, 0, 0, &kval, 0, 0)); + test.run(HStmt(hStmt), SQLBindCol(hStmt, 1, SQL_C_UBIGINT, &aval, 0, 0)); + test.run(HStmt(hStmt), SQLBindCol(hStmt, 2, SQL_C_SBIGINT, &bval, 0, 0)); + for (SQLBIGINT i = 0; i < num; i++) { + kval = i * i * i * i * i * i * i * i * i * i; // 10 + if (opt.m_v >= 3) + ndbout << "fetch " << i << " key " << kval << endl; + test.run(HStmt(hStmt), SQLExecute(hStmt)); + aval = bval = 0; + test.run(HStmt(hStmt), SQLFetch(hStmt)); + test.chk(HStmt(hStmt), aval == kval && bval == -kval, "got %llu, %lld != %llu, %lld", aval, bval, kval, -kval); + test.exp(SQL_NO_DATA, 0, 0, true); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); + } + freeAll(test, hEnv, hDbc, hStmt); +} + +static void +testMiscC2(Test& test) +{ + SQLHANDLE hEnv, hDbc, hStmt; + allocAll(test, hEnv, hDbc, hStmt); +#if 0 + { + char POP[255]; + char PORT[255]; + char ACCESSNODE[255]; + + const char* sqlSelect = "select PORT from AAA where POP=? and ACCESSNODE=?"; + + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sqlSelect, SQL_NTS)); + + for (int j=0; j<5; j++) { + printf("Loop %u\n", j); + printf("LINE %u\n", __LINE__); + + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 255, 0, POP, 255, 0)); + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 255, 0, ACCESSNODE, 255, 0)); + test.run(HStmt(hStmt), SQLBindCol(hStmt, 1, SQL_C_CHAR, PORT, 255, 0)); + + sprintf(POP, "a"); + sprintf(ACCESSNODE, "b"); + + test.run(HStmt(hStmt), SQLExecute(hStmt)); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + printf("got %s\n", PORT); + printf("LINE %u\n", __LINE__); + test.exp(SQL_NO_DATA, 0, 0, true); + printf("LINE %u\n", __LINE__); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + printf("LINE %u\n", __LINE__); + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); + printf("LINE %u\n", __LINE__); + } + } + return; +#endif + + char POP[255]; + char PORT[255]; + char ACCESSNODE[255]; + unsigned long VLAN = 0; + unsigned long SNMP_INDEX = 0; + unsigned long PORT_STATE = 0; + unsigned long STATIC_PORT = 0; + unsigned long COMMENT = 0; + + const char* sqlSelect = "select PORT, PORT_STATE from PORTS where POP=? and ACCESSNODE=?"; + + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sqlSelect, SQL_NTS)); + + for (int j=0; j<5; j++) { + printf("Loop %u\n", j); + printf("LINE %u\n", __LINE__); + + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 255, 0, POP, 255, 0)); + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 255, 0, ACCESSNODE, 255, 0)); + test.run(HStmt(hStmt), SQLBindCol(hStmt, 1, SQL_C_CHAR, PORT, 255, 0)); + test.run(HStmt(hStmt), SQLBindCol(hStmt, 2, SQL_C_ULONG, &PORT_STATE, 0, 0)); + + sprintf(POP, "row%u.i%u.bredband.com", 2, 3); + sprintf(ACCESSNODE, "as%u", 2); + + test.run(HStmt(hStmt), SQLExecute(hStmt)); + for (int i=0; i < 3; i++) { + PORT_STATE=0; + sprintf(PORT, "XXXXXXXXXXXXXXXXXXXXX"); + + test.run(HStmt(hStmt), SQLFetch(hStmt)); + printf("got %s %lu\n", PORT, PORT_STATE); + // test.chk(HStmt(hStmt), false, "got %s != %s", "xxx", PORT); + } + printf("LINE %u\n", __LINE__); + test.exp(SQL_NO_DATA, 0, 0, true); + printf("LINE %u\n", __LINE__); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + printf("LINE %u\n", __LINE__); + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); + printf("LINE %u\n", __LINE__); + } +} + +static const Sql +miscSqlC2[] = { + Sql("drop table PORTS", + SQL_DIAG_DROP_TABLE, -1, 0, -1, -1, + SQL_ERROR, "IM000", 2040709, false), + Sql("create table PORTS (POP varchar(200) not null, ACCESSNODE varchar(200) not null, PORT varchar(200) not null, VLAN int unsigned, SNMP_INDEX int unsigned, PORT_STATE int unsigned, STATIC_PORT int unsigned, COMMENT int unsigned, primary key (POP,ACCESSNODE,PORT))", + SQL_DIAG_CREATE_TABLE, -1, 0, -1, -1), + Sql("create index xxPORTS on PORTS (POP, ACCESSNODE) nologging", + SQL_DIAG_CREATE_INDEX, -1, -1, -1, -1), + Sql("insert into PORTS values ('row2.i3.bredband.com','as2','Fa0/0',0,1,2,3,4)", + SQL_DIAG_INSERT, 1, 0, -1, -1), + Sql("insert into PORTS values ('row2.i3.bredband.com','as2','Fa0/1',1,2,3,4,5)", + SQL_DIAG_INSERT, 1, 0, -1, -1), + Sql("insert into PORTS values ('row2.i3.bredband.com','as2','Fa0/2',2,3,4,5,6)", + SQL_DIAG_INSERT, 1, 0, -1, -1), + Sql("select PORT, PORT_STATE from PORTS where POP='row2.i3.bredband.com' and ACCESSNODE='as2'", + SQL_DIAG_SELECT_CURSOR, 3, 3, -1, -1), + + Sql("drop table AAA", + SQL_DIAG_DROP_TABLE, -1, 0, -1, -1, + SQL_ERROR, "IM000", 2040709, false), + Sql("create table AAA (POP varchar(200), ACCESSNODE varchar(200) not null, PORT varchar(200) not null, primary key (POP,ACCESSNODE,PORT))", + SQL_DIAG_CREATE_TABLE, -1, 0, -1, -1), + Sql("create index xxAAA on AAA (POP, ACCESSNODE) nologging", + SQL_DIAG_CREATE_INDEX, -1, -1, -1, -1), + Sql("insert into AAA values ('a','b','A')", + SQL_DIAG_INSERT, 1, 0, -1, -1), + + Sql("testMiscC2", testMiscC2), + Sql() +}; + +/* +> SELECT PORT, PORT_STATE FROM PORTS where pop=? and accessnode=? +> SELECT VLAN, SNMP_INDEX, PORT_STATE, STATIC_PORT, COMMENT FROM PORTS WHERE POP=? AND ACCESSNODE=? AND PORT=? +> select count(*) from ports +> select snmp_index from ports where pop='row2.i3.bredband.com' and accessnode='as2' and port='Fa0/2' + +> SELECT MAC, MAC_EXPIRE, IP, IP_EXPIRE, HOSTNAME, DETECTED, STATUS, STATIC_DNS, BLOCKED, NUM_REQUESTS, ACCESSTYPE, OS_TYPE, GATE_WAY, DIRTY_FLAG, LOCKED_IP FROM CLIENTS WHERE PORT=? AND ACCESSNODE=? AND POP=? +> SELECT SERVICES.ACCESSTYPE, SERVICES.NUM_IP, SERVICES.TEXPIRE, SERVICES.CUSTOMER_ID, SERVICES.LEASED_NUM_IP, SERVICES.PROVIDER, SERVICES.LOCKED_IP, SERVICES.STATIC_DNS, SERVICES.SUSPENDED_SERVICE FROM SERVICES , ACCESSTYPES WHERE SERVICES.PORT = ? AND SERVICES.ACCESSNODE = ? AND SERVICES.POP = ? AND SERVICES.ACCESSTYPE=ACCESSTYPES.ACCESSTYPE +*/ + +static const Sql +miscSql99[] = { + Sql("drop table tt99a", + SQL_DIAG_DROP_TABLE, -1, 0, -1, -1, + SQL_ERROR, "IM000", 2040709, false), + Sql("create table tt99a (col1 int unsigned primary key, col2 int unsigned, col3 int unsigned, col4 int unsigned, col5 int unsigned, col6 varchar(7) default 'abc123')", + SQL_DIAG_CREATE_TABLE, -1, 0, -1, -1), + // inserts 10 rows, all same, start value 1000000000 + Sql("testMisc99a", testMisc99a), + // interpreted scan plus bind parameter + Sql("select col1 from tt99a where col2 = ?", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1000000004, 1000000004), + Sql("select col1 from tt99a where col2 = 1000000000 + ?", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1000000004, 4), + Sql("select col1 from tt99a where col2 = ? + 1000000000", + SQL_DIAG_SELECT_CURSOR, 1, 1, 1000000004, 4), + // same not interpreted, tuple count 10 + Sql("select col1 from tt99a where col2 + 0 = 1000000000 + ?", + SQL_DIAG_SELECT_CURSOR, 1, 10, 1000000004, 4), + // varchar variations + Sql("select count(*) from tt99a where col6 = 'abc123'", + SQL_DIAG_SELECT_CURSOR, 1, 10, 10, -1), + Sql("select count(*) from tt99a where left(col6, ?) = 'abc1'", + SQL_DIAG_SELECT_CURSOR, 1, 10, 10, 4), + Sql("select count(*) from tt99a where left(col6, ?) = 'abc1'", + SQL_DIAG_SELECT_CURSOR, 1, 10, 0, 3), + // tpc-b inspired, wrong optimization to direct update + Sql("drop table tt99b", + SQL_DIAG_DROP_TABLE, -1, 0, -1, -1, + SQL_ERROR, "IM000", 2040709, false), + Sql("create table tt99b(a int primary key, b int not null, c double precision)", + SQL_DIAG_CREATE_TABLE, -1, 0, -1, -1), + Sql("insert into tt99b values(1, 10, 100.0)", + SQL_DIAG_INSERT, 1, 0, -1, -1), + Sql("insert into tt99b values(9, 90, 900.0)", + SQL_DIAG_INSERT, 1, 0, -1, -1), + Sql("create unique hash index tt99y on tt99b (b)", + SQL_DIAG_CREATE_INDEX, -1, 0, -1, -1), + // first scan update.. + Sql("update tt99b set c = c + ? where a+0 = 1", + SQL_DIAG_UPDATE_WHERE, 1, 2, -1, 10), + Sql("update tt99b set c = c + ? where b+0 = 10", + SQL_DIAG_UPDATE_WHERE, 1, 2, -1, 10), + // then optimized.. + Sql("update tt99b set c = c + ? where a = 1", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, 10), + Sql("update tt99b set c = c + ? where b = 10", + SQL_DIAG_UPDATE_WHERE, 1, 1, -1, 10), + // verify.. + Sql("select count(*) from tt99b where 100-1 < c and c < 140-1", + SQL_DIAG_SELECT_CURSOR, 1, 2, 0, -1), + Sql("select count(*) from tt99b where 140-.001 < c and c < 140+.001", + SQL_DIAG_SELECT_CURSOR, 1, 2, 1, -1), + // unsigned test + Sql("drop table tt99c", + SQL_DIAG_DROP_TABLE, -1, 0, -1, -1, + SQL_ERROR, "IM000", 2040709, false), + Sql("create table tt99c(a int unsigned primary key, b int unsigned)", + SQL_DIAG_CREATE_TABLE, -1, 0, -1, -1), + Sql("insert into tt99c values(2100000000, 4100000000)", + SQL_DIAG_INSERT, 1, 0, -1, -1), + Sql("insert into tt99c (a, b) select b, a from tt99c", + SQL_DIAG_INSERT, 1, 1, -1, -1), + Sql("testMisc99c", testMisc99c), + // new external type SQL_C_[SU]BIGINT + Sql("testMisc99d", testMisc99d), + Sql() +}; + +static const struct { const Sql* sql; int minscale; } +miscSql[11] = { + { miscSql90, 0 }, + { miscSql91, 0 }, + { miscSql92, 0 }, + { miscSql93, 0 }, + { 0, 0 }, // 94 + { miscSql95, 0 }, + { miscSql96, 0 }, + { miscSql97, 0 }, + { 0, 0 }, // 98 + { miscSql99, 0 }, + { miscSqlC2, 0 } +}; + +static void +testSql(Test& test) +{ + const unsigned salt = test.m_stuff; // mess + if (opt.m_scale < miscSql[salt].minscale) { + if (opt.m_v >= 1) + ndbout << "skip - requires scale >= " << miscSql[salt].minscale << endl; + return; + } + assert(0 <= salt && salt < 11 && miscSql[salt].sql != 0); + SQLHANDLE hEnv, hDbc, hStmt; + allocAll(test, hEnv, hDbc, hStmt); + for (unsigned i = 0; ; i++) { + const Sql& sql = miscSql[salt].sql[i]; + if (sql.m_sql == 0) + break; + if (opt.m_v >= 2) + ndbout << "SQL: " << sql.m_sql << endl; + if (sql.m_testFunc != 0) { + (*sql.m_testFunc)(test); + continue; + } + if (strncmp(sql.m_sql, "--", 2) == 0) { + continue; + } + if (strcmp(sql.m_sql, Sql::set_autocommit_on()) == 0) { + setAutocommit(test, hDbc, true); + continue; + } + if (strcmp(sql.m_sql, Sql::set_autocommit_off()) == 0) { + setAutocommit(test, hDbc, false); + continue; + } + if (strcmp(sql.m_sql, Sql::do_commit()) == 0) { + test.run(HDbc(hDbc), SQLEndTran(SQL_HANDLE_DBC, hDbc, SQL_COMMIT)); + continue; + } + if (strcmp(sql.m_sql, Sql::do_rollback()) == 0) { + test.run(HDbc(hDbc), SQLEndTran(SQL_HANDLE_DBC, hDbc, SQL_ROLLBACK)); + continue; + } + if (opt.m_v >= 3) { + ndbout << "expect:"; + ndbout << " ret=" << sql.m_ret; + ndbout << " rows=" << sql.m_rowCount; + ndbout << " tuples=" << sql.m_tuplesFetched; + ndbout << endl; + } + test.run(HStmt(hStmt), SQLFreeStmt(hStmt, SQL_UNBIND)); + test.run(HStmt(hStmt), SQLFreeStmt(hStmt, SQL_RESET_PARAMS)); + // prep + test.exp(sql.m_ret, sql.m_state, sql.m_native, false); + test.run(HStmt(hStmt), SQLPrepare(hStmt, (SQLCHAR*)sql.m_sql, SQL_NTS)); + if (test.m_ret != SQL_SUCCESS) + continue; + // bind between prep and exec like JDBC + unsigned long bindValue = sql.m_bindValue; + for (int k = 0; k <= 1; k++) { + if (bindValue != -1) { + assert(strchr(sql.m_sql, '?') != 0); + test.run(HStmt(hStmt), SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &bindValue, 0, 0)); + } + if (k == 0) { + if (bindValue != -1) { + test.run(HStmt(hStmt), SQLFreeStmt(hStmt, SQL_RESET_PARAMS)); + // exec with unbound parameter + test.exp(SQL_ERROR, "HY010", -1, true); + test.run(HStmt(hStmt), SQLExecute(hStmt)); + test.chk(HStmt(hStmt), test.m_functionCode == sql.m_functionCode || sql.m_functionCode == -1, "func: got %d != %d", (int)test.m_functionCode, (int)sql.m_functionCode); + } + } else { + // exec + test.exp(sql.m_ret, sql.m_state, sql.m_native, sql.m_reset); + test.run(HStmt(hStmt), SQLExecute(hStmt)); + test.chk(HStmt(hStmt), test.m_functionCode == sql.m_functionCode || sql.m_functionCode == -1, "func: got %d != %d", (int)test.m_functionCode, (int)sql.m_functionCode); + } + } + if (sql.m_rowCount != -1) { + if (sql.m_functionCode == SQL_DIAG_SELECT_CURSOR) { + long lastValue; + if (sql.m_lastValue != -1) + test.run(HStmt(hStmt), SQLBindCol(hStmt, 1, SQL_C_SLONG, &lastValue, 0, 0)); + unsigned k = 0; + do { + int rowCount = 0; + lastValue = -1; + while (1) { + test.exp(SQL_NO_DATA, 0, 0, false); + test.run(HStmt(hStmt), SQLFetch(hStmt)); + if (test.m_ret == SQL_NO_DATA) + break; + rowCount++; + } + test.chk(HStmt(hStmt), rowCount == sql.m_rowCount, "rowCount: got %d != %d", (int)rowCount, (int)sql.m_rowCount); + if (sql.m_tuplesFetched != -1) + chkTuplesFetched(test, hStmt, sql.m_tuplesFetched); + if (rowCount > 0 && sql.m_lastValue != -1) + test.chk(HStmt(hStmt), lastValue == sql.m_lastValue, "lastValue: got %ld != %ld", (long)lastValue, (long)sql.m_lastValue); + test.run(HStmt(hStmt), SQLCloseCursor(hStmt)); + if (++k >= opt.m_scale) + break; + test.run(HStmt(hStmt), SQLExecute(hStmt)); + } while (1); + test.timerCnt(opt.m_scale); + } else { + assert(sql.m_lastValue == -1); + chkRowCount(test, hStmt, sql.m_rowCount); + if (sql.m_tuplesFetched != -1) + chkTuplesFetched(test, hStmt, sql.m_tuplesFetched); + test.timerCnt(1); + } + } + } + freeAll(test, hEnv, hDbc, hStmt); +} + +// name, function, runmode, salt (0=const or n/a), description +static const Case caseList[] = { + Case( "00alloc", testAlloc, Case::Thread, 0, "allocate handles" ), + Case( "01create", testCreate, Case::Single, 0, "create tables for the test" ), + Case( "02prepare", testPrepare, Case::Thread, 0, "prepare without execute" ), + Case( "03catalog", testCatalog, Case::Thread, 0, "catalog functions" ), + Case( "10insert", testInsert, Case::Thread, 1, "insert computed rows" ), + Case( "11delall", testDeleteAll, Case::Single, 0, "delete all rows via scan" ), + Case( "12insert", testInsert, Case::Thread, 1, "insert computed rows again" ), + Case( "13count", testCount, Case::Single, 0, "count rows" ), + Case( "14verpk", testVerifyPk, Case::Thread, 1, "verify via primary key" ), + Case( "15verscan", testVerifyScan, Case::Serial, 1, "verify via range scans" ), + Case( "16join", testJoin, Case::Single, 0, "multiple self-join" ), + Case( "17cart", testCart, Case::Single, 0, "cartesian join" ), + Case( "20updpk", testUpdatePk, Case::Thread, 2, "update via primary key" ), + Case( "21verpk", testVerifyPk, Case::Thread, 2, "verify via primary key" ), + Case( "22verscan", testVerifyScan, Case::Serial, 2, "verify via range scans" ), + Case( "23updscan", testUpdateScan, Case::Serial, 0, "update via scan" ), + Case( "24verpk", testVerifyPk, Case::Thread, 0, "verify via primary key" ), + Case( "25verscan", testVerifyScan, Case::Serial, 0, "verify via range scans" ), + Case( "26delpk", testDeletePk, Case::Thread, 0, "delete via primary key" ), + Case( "30trans", testTrans, Case::Single, 3, "rollback and commit" ), + Case( "31concur", testConcur, Case::Single, 0, "commit across open cursor" ), + Case( "32readcom", testReadcom, Case::Single, 0, "read committed" ), + Case( "40perf", testPerf, Case::Single, 0, "perf test prepare" ), + Case( "41perf", testPerf, Case::Thread, 1, "perf test NDB API" ), + Case( "42perf", testPerf, Case::Thread, 2, "perf test NDB ODBC" ), + Case( "90sql", testSql, Case::Single, 0, "misc SQL: metadata" ), + Case( "91sql", testSql, Case::Single, 1, "misc SQL: misc" ), + Case( "92sql", testSql, Case::Thread, 2, "misc SQL: scan rollback" ), + Case( "93sql", testSql, Case::Single, 3, "misc SQL: locking" ), + Case( "95sql", testSql, Case::Single, 5, "misc SQL: indexes (simple)" ), + Case( "96sql", testSql, Case::Single, 6, "misc SQL: indexes" ), + Case( "97sql", testSql, Case::Thread, 7, "misc SQL: indexes" ), + Case( "99sql", testSql, Case::Single, 9, "misc SQL: bug du jour" ), + Case( "C2", testSql, Case::Single, 10, "misc SQL: C2" ) +}; + +static const unsigned caseCount = arraySize(caseList); + +static bool +findCase(const char* name) +{ + for (unsigned i = 0; i < caseCount; i++) { + const Case& cc = caseList[i]; + if (strstr(cc.m_name, name) != 0) + return true; + } + return false; +} + +static void +listCases() +{ + ndbout << "cases:" << endl; + unsigned m = 0; + for (unsigned i = 0; i < caseCount; i++) { + const Case& cc = caseList[i]; + if (m < strlen(cc.m_name)) + m = strlen(cc.m_name); + } + for (unsigned i = 0; i < caseCount; i++) { + const Case& cc = caseList[i]; + char buf[200]; + sprintf(buf, "%-*s [%-6s] %s", m, cc.m_name, cc.modename(), cc.m_desc); + ndbout << buf << endl; + } +} + +// threads + +extern "C" { static void* testThr(void* arg); } + +struct Thr { + enum State { + Wait = 1, // wait for test case + Run = 2, // run the test case + Done = 3, // done with the case + Exit = 4 // exit thread + }; + unsigned m_no; // thread number 1 .. max + NdbThread* m_thr; // thread id etc + const Case* m_case; // current case + State m_state; // condition variable + NdbMutex* m_mutex; // condition guard + NdbCondition* m_cond; + void* m_status; // exit status (not used) + Test m_test; // test runner + Thr(unsigned no, unsigned loop) : + m_no(no), + m_thr(0), + m_case(0), + m_state(Wait), + m_mutex(NdbMutex_Create()), + m_cond(NdbCondition_Create()), + m_status(0), + m_test(no, loop) { + } + ~Thr() { + destroy(); + NdbCondition_Destroy(m_cond); + NdbMutex_Destroy(m_mutex); + } + void create() { + assert(m_thr == 0); + m_thr = NdbThread_Create(testThr, (void**)this, 64*1024, "test", NDB_THREAD_PRIO_LOW); + } + void destroy() { + if (m_thr != 0) + NdbThread_Destroy(&m_thr); + m_thr = 0; + } + void lock() { + NdbMutex_Lock(m_mutex); + } + void unlock() { + NdbMutex_Unlock(m_mutex); + } + void wait() { + NdbCondition_Wait(m_cond, m_mutex); + } + void signal() { + NdbCondition_Signal(m_cond); + } + void join() { + NdbThread_WaitFor(m_thr, &m_status); + m_thr = 0; + } + // called from main + void mainStart(const Case& cc) { + lock(); + m_case = &cc; + m_state = Run; + signal(); + unlock(); + } + void mainStop() { + lock(); + while (m_state != Done) { + if (opt.m_v >= 4) + ndbout << ::lock << "thr " << m_no << " [main] wait state=" << m_state << endl << ::unlock; + wait(); + } + if (opt.m_v >= 4) + ndbout << ::lock << "thr " << m_no << " [main] done" << endl << ::unlock; + m_state = Wait; + unlock(); + } + // run in thread + void testSelf() { + while (1) { + lock(); + while (m_state != Run && m_state != Exit) { + if (opt.m_v >= 4) + ndbout << ::lock << "thr " << m_no << " [self] wait state=" << m_state << endl << ::unlock; + wait(); + } + if (m_state == Run) { + if (opt.m_v >= 4) + ndbout << ::lock << "thr " << m_no << " [self] run" << endl << ::unlock; + assert(m_case != 0); + m_test.timerOn(); + m_test.runCase(*m_case); + m_test.timerOff(); + m_state = Done; + if (opt.m_v >= 4) + ndbout << ::lock << "thr " << m_no << " [self] done" << endl << ::unlock; + signal(); + unlock(); + } else if (m_state == Exit) { + unlock(); + break; + } else { + assert(false); + } + } + if (opt.m_v >= 4) + ndbout << ::lock << "thr " << m_no << " [self] exit" << endl << ::unlock; + } +}; + +static void* +testThr(void* arg) +{ + Thr& thr = *(Thr*)arg; + thr.testSelf(); + return 0; +} + +#ifdef DMALLOC +extern "C" { + +static int malloc_bytes = 0; +static int free_bytes = 0; + +static void +malloc_track(const char *file, const unsigned int line, const int func_id, const DMALLOC_SIZE byte_size, const DMALLOC_SIZE alignment, const DMALLOC_PNT old_addr, const DMALLOC_PNT new_addr) +{ + if (func_id == DMALLOC_FUNC_MALLOC) { + malloc_bytes += byte_size; + return; + } + if (func_id == DMALLOC_FUNC_FREE) { + DMALLOC_SIZE size = 0; + dmalloc_examine(old_addr, &size, 0, 0, 0); + free_bytes += size; + // XXX useless - byte_size and size are 0 + return; + } +} + +} +#endif /* DMALLOC */ + +static void +testMain() +{ +#ifndef NDB_LINUX /* valgrind-1.0.3 does not support */ + NdbThread_SetConcurrencyLevel(opt.m_threads + 2); +#endif +#ifdef DMALLOC + dmalloc_track(malloc_track); +#endif + Test test(0, 0); +#ifdef ndbODBC + Ndb* ndb = 0; + if (1) { // pre-alloc one Ndb object + ndb = new Ndb("TEST_DB"); + ndb->init(); + if (ndb->waitUntilReady() != 0) { + ndbout << ndb->getNdbError() << endl; + fatal("waitUntilReady"); + } + } +#endif + for (unsigned loop = 1; opt.m_loop == 0 || loop <= opt.m_loop; loop++) { + if (opt.m_v >= 2) + ndbout << "loop " << loop << endl; + // create new set of threads in each loop + Thr** thrList = new Thr* [1 + opt.m_threads]; + for (unsigned n = 1; n <= opt.m_threads; n++) { + Thr& thr = *(thrList[n] = new Thr(n, loop)); + thr.create(); + if (opt.m_v >= 4) + ndbout << "thr " << n << " [main] created" << endl; + } +#ifdef DMALLOC + malloc_bytes = free_bytes = 0; +#endif + for (unsigned i = 0; i < caseCount; i++) { + const Case& cc = caseList[i]; + if (! cc.matchcase()) + continue; + if (opt.m_v >= 2) + ndbout << "RUN: " << cc.m_name << " - " << cc.m_desc << endl; + test.timerOn(); + for (unsigned subloop = 1; subloop <= opt.m_subloop; subloop++) { + my_sema = 0; + if (opt.m_v >= 3) + ndbout << "subloop " << subloop << endl; + if (cc.m_mode == Case::Single) { + Thr& thr = *thrList[1]; + thr.mainStart(cc); + thr.mainStop(); + test.timerCnt(thr.m_test); + } else if (cc.m_mode == Case::Serial) { + for (unsigned n = 1; n <= opt.m_threads; n++) { + Thr& thr = *thrList[n]; + thr.mainStart(cc); + thr.mainStop(); + test.timerCnt(thr.m_test); + } + } else if (cc.m_mode == Case::Thread) { + for (unsigned n = 1; n <= opt.m_threads; n++) { + Thr& thr = *thrList[n]; + thr.mainStart(cc); + } + for (unsigned n = 1; n <= opt.m_threads; n++) { + Thr& thr = *thrList[n]; + thr.mainStop(); + test.timerCnt(thr.m_test); + } + } else { + assert(false); + } + } + test.timerOff(); + if (opt.m_v >= 1) + ndbout << cc.m_name << " total " << test << endl; + } +#ifdef DMALLOC + if (opt.m_v >= 9) // XXX useless now + ndbout << "malloc " << malloc_bytes << " free " << free_bytes << " lost " << malloc_bytes - free_bytes << endl; +#endif + // tell threads to exit + for (unsigned n = 1; n <= opt.m_threads; n++) { + Thr& thr = *thrList[n]; + thr.lock(); + thr.m_state = Thr::Exit; + thr.signal(); + thr.unlock(); + if (opt.m_v >= 4) + ndbout << "thr " << n << " [main] told to exit" << endl; + } + for (unsigned n = 1; n <= opt.m_threads; n++) { + Thr& thr = *thrList[n]; + thr.join(); + if (opt.m_v >= 4) + ndbout << "thr " << n << " [main] joined" << endl; + delete &thr; + } + delete[] thrList; + } +#ifdef ndbODBC + delete ndb; +#endif +} + +static bool +str2num(const char* arg, const char* str, unsigned* num, unsigned lo = 0, unsigned hi = 0) +{ + char* end = 0; + long n = strtol(str, &end, 0); + if (end == 0 || *end != 0 || n < 0) { + ndbout << arg << " " << str << " is invalid number" << endl; + return false; + } + if (lo != 0 && n < lo) { + ndbout << arg << " " << str << " is too small min = " << lo << endl; + return false; + } + if (hi != 0 && n > hi) { + ndbout << arg << " " << str << " is too large max = " << hi << endl; + return false; + } + *num = n; + return true; +} + +NDB_COMMAND(testOdbcDriver, "testOdbcDriver", "testOdbcDriver", "testOdbcDriver", 65535) +{ + while (++argv, --argc > 0) { + const char* arg = argv[0]; + if (strcmp(arg, "-case") == 0) { + if (++argv, --argc > 0) { + assert(opt.m_namecnt < arraySize(opt.m_name)); + opt.m_name[opt.m_namecnt++] = argv[0]; + if (findCase(argv[0])) + continue; + } + } + if (strcmp(arg, "-core") == 0) { + opt.m_core = true; + continue; + } + if (strcmp(arg, "-depth") == 0) { + if (++argv, --argc > 0) { + if (str2num(arg, argv[0], &opt.m_depth)) + continue; + } + } + if (strcmp(arg, "-dsn") == 0) { + if (++argv, --argc > 0) { + opt.m_dsn = argv[0]; + continue; + } + } + if (strcmp(arg, "-frob") == 0) { + if (++argv, --argc > 0) { + if (str2num(arg, argv[0], &opt.m_frob)) + continue; + } + } + if (strcmp(arg, "-errs") == 0) { + if (++argv, --argc > 0) { + if (str2num(arg, argv[0], &opt.m_errs)) + continue; + } + } + if (strcmp(arg, "-fragtype") == 0) { + if (++argv, --argc > 0) { + opt.m_fragtype = argv[0]; + continue; + } + } + if (strcmp(arg, "-home") == 0) { + if (++argv, --argc > 0) { + opt.m_home = argv[0]; + continue; + } + } + if (strcmp(arg, "-loop") == 0) { + if (++argv, --argc > 0) { + if (str2num(arg, argv[0], &opt.m_loop)) + continue; + } + } + if (strcmp(arg, "-nogetd") == 0) { + opt.m_nogetd = true; + continue; + } + if (strcmp(arg, "-noputd") == 0) { + opt.m_noputd = true; + continue; + } + if (strcmp(arg, "-nosort") == 0) { + opt.m_nosort = true; + continue; + } + if (strcmp(arg, "-scale") == 0) { + if (++argv, --argc > 0) { + if (str2num(arg, argv[0], &opt.m_scale)) + continue; + } + } + if (strcmp(arg, "-serial") == 0) { + opt.m_serial = true; + continue; + } + if (strcmp(arg, "-skip") == 0) { + if (++argv, --argc > 0) { + assert(opt.m_skipcnt < arraySize(opt.m_skip)); + opt.m_skip[opt.m_skipcnt++] = argv[0]; + if (findCase(argv[0])) + continue; + } + } + if (strcmp(arg, "-subloop") == 0) { + if (++argv, --argc > 0) { + if (str2num(arg, argv[0], &opt.m_subloop)) + continue; + } + } + if (strcmp(arg, "-table") == 0) { + if (++argv, --argc > 0) { + opt.m_table = argv[0]; + if (findTable()) + continue; + } + } + if (strcmp(arg, "-threads") == 0) { + if (++argv, --argc > 0) { + if (str2num(arg, argv[0], &opt.m_threads, 1, MAX_THR)) + continue; + } + } + if (strcmp(arg, "-trace") == 0) { + if (++argv, --argc > 0) { + if (str2num(arg, argv[0], &opt.m_trace)) + continue; + } + } + if (strcmp(arg, "-v") == 0) { + if (++argv, --argc > 0) { + if (str2num(arg, argv[0], &opt.m_v)) + continue; + } + } + if (strncmp(arg, "-v", 2) == 0 && isdigit(arg[2])) { + if (str2num(arg, &arg[2], &opt.m_v)) + continue; + } + printusage(); + return NDBT_ProgramExit(NDBT_WRONGARGS); + } + homeEnv: { + static char env[1000]; + if (opt.m_home != 0) { + sprintf(env, "NDB_HOME=%s", opt.m_home); + putenv(env); + } + } + traceEnv: { + static char env[40]; + sprintf(env, "NDB_ODBC_TRACE=%u", opt.m_trace); + putenv(env); + } + debugEnv: { + static char env[40]; + sprintf(env, "NDB_ODBC_DEBUG=%d", 1); + putenv(env); + } + testMain(); + return NDBT_ProgramExit(NDBT_OK); +} + +// vim: set sw=4: |