summaryrefslogtreecommitdiff
path: root/storage/ndb/test/odbc/driver/testOdbcDriver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'storage/ndb/test/odbc/driver/testOdbcDriver.cpp')
-rw-r--r--storage/ndb/test/odbc/driver/testOdbcDriver.cpp4964
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: