mariadb/ndb/test/odbc/driver/testOdbcDriver.cpp
unknown d6cf9dd9b7 fix ndb odbc compile
BitKeeper/etc/logging_ok:
  Logging to logging@openlogging.org accepted
2004-05-18 09:58:30 +02:00

4964 lines
158 KiB
C++
Raw Blame History

/* 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<66>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: