/* 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; version 2 of the License. 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 */ /* * testOIBasic - ordered index test */ #include #include #include #include #include #include #include #include #include #include #include #include #include // options struct Opt { // common options unsigned m_batch; const char* m_bound; const char* m_case; bool m_collsp; bool m_core; const char* m_csname; CHARSET_INFO* m_cs; int m_die; bool m_dups; NdbDictionary::Object::FragmentType m_fragtype; const char* m_index; unsigned m_loop; bool m_msglock; bool m_nologging; bool m_noverify; unsigned m_pctnull; unsigned m_rows; unsigned m_samples; unsigned m_scanbatch; unsigned m_scanpar; unsigned m_scanstop; int m_seed; unsigned m_subloop; const char* m_table; unsigned m_threads; int m_v; // int for lint Opt() : m_batch(32), m_bound("01234"), m_case(0), m_collsp(false), m_core(false), m_csname("random"), m_cs(0), m_die(0), m_dups(false), m_fragtype(NdbDictionary::Object::FragUndefined), m_index(0), m_loop(1), m_msglock(true), m_nologging(false), m_noverify(false), m_pctnull(10), m_rows(1000), m_samples(0), m_scanbatch(0), m_scanpar(0), m_scanstop(0), m_seed(-1), m_subloop(4), m_table(0), m_threads(4), m_v(1) { } }; static Opt g_opt; static void printcases(); static void printtables(); static void printhelp() { Opt d; ndbout << "usage: testOIbasic [options]" << endl << " -batch N pk operations in batch [" << d.m_batch << "]" << endl << " -bound xyz use only these bound types 0-4 [" << d.m_bound << "]" << endl << " -case abc only given test cases (letters a-z)" << endl << " -collsp use strnncollsp instead of strnxfrm" << endl << " -core core dump on error [" << d.m_core << "]" << endl << " -csname S charset or collation [" << d.m_csname << "]" << endl << " -die nnn exit immediately on NDB error code nnn" << endl << " -dups allow duplicate tuples from index scan [" << d.m_dups << "]" << endl << " -fragtype T fragment type single/small/medium/large" << endl << " -index xyz only given index numbers (digits 0-9)" << endl << " -loop N loop count full suite 0=forever [" << d.m_loop << "]" << endl << " -nologging create tables in no-logging mode" << endl << " -noverify skip index verifications" << endl << " -pctnull N pct NULL values in nullable column [" << d.m_pctnull << "]" << endl << " -rows N rows per thread [" << d.m_rows << "]" << endl << " -samples N samples for some timings (0=all) [" << d.m_samples << "]" << endl << " -scanbatch N scan batch 0=default [" << d.m_scanbatch << "]" << endl << " -scanpar N scan parallel 0=default [" << d.m_scanpar << "]" << endl << " -seed N srandom seed 0=loop number -1=random [" << d.m_seed << "]" << endl << " -subloop N subtest (and subsubtest) loop count [" << d.m_subloop << "]" << endl << " -table xyz only given table numbers (digits 0-9)" << endl << " -threads N number of threads [" << d.m_threads << "]" << endl << " -vN verbosity [" << d.m_v << "]" << endl << " -h or -help print this help text" << endl ; printcases(); printtables(); } // not yet configurable static const bool g_store_null_key = true; // compare NULL like normal value (NULL < not NULL, NULL == NULL) static const bool g_compare_null = true; static const char* hexstr = "0123456789abcdef"; // random ints static unsigned urandom(unsigned n) { if (n == 0) return 0; unsigned i = random() % n; return i; } static int irandom(unsigned n) { if (n == 0) return 0; int i = random() % n; if (random() & 0x1) i = -i; return i; } static bool randompct(unsigned pct) { if (pct == 0) return false; if (pct >= 100) return true; return urandom(100) < pct; } static unsigned random_coprime(unsigned n) { unsigned prime[] = { 101, 211, 307, 401, 503, 601, 701, 809, 907 }; unsigned count = sizeof(prime) / sizeof(prime[0]); if (n == 0) return 0; while (1) { unsigned i = urandom(count); if (n % prime[i] != 0) return prime[i]; } } // random re-sequence of 0...(n-1) struct Rsq { Rsq(unsigned n); unsigned next(); private: unsigned m_n; unsigned m_i; unsigned m_start; unsigned m_prime; }; Rsq::Rsq(unsigned n) { m_n = n; m_i = 0; m_start = urandom(n); m_prime = random_coprime(n); } unsigned Rsq::next() { assert(m_n != 0); return (m_start + m_i++ * m_prime) % m_n; } // log and error macros static NdbMutex *ndbout_mutex = NULL; static unsigned getthrno(); static const char* getthrstr() { static char buf[20]; unsigned n = getthrno(); if (n == (unsigned)-1) strcpy(buf, ""); else { unsigned m = g_opt.m_threads < 10 ? 1 : g_opt.m_threads < 100 ? 2 : 3; sprintf(buf, "[%0*u] ", m, n); } return buf; } #define LLN(n, s) \ do { \ if ((n) > g_opt.m_v) break; \ if (g_opt.m_msglock) NdbMutex_Lock(ndbout_mutex); \ ndbout << getthrstr() << s << endl; \ if (g_opt.m_msglock) NdbMutex_Unlock(ndbout_mutex); \ } while(0) #define LL0(s) LLN(0, s) #define LL1(s) LLN(1, s) #define LL2(s) LLN(2, s) #define LL3(s) LLN(3, s) #define LL4(s) LLN(4, s) #define LL5(s) LLN(5, s) // following check a condition and return -1 on failure #undef CHK // simple check #undef CHKTRY // check with action on fail #undef CHKCON // print NDB API errors on failure #define CHK(x) CHKTRY(x, ;) #define CHKTRY(x, act) \ do { \ if (x) break; \ LL0("line " << __LINE__ << ": " << #x << " failed"); \ if (g_opt.m_core) abort(); \ act; \ return -1; \ } while (0) #define CHKCON(x, con) \ do { \ if (x) break; \ LL0("line " << __LINE__ << ": " << #x << " failed"); \ (con).printerror(ndbout); \ if (g_opt.m_core) abort(); \ return -1; \ } while (0) // method parameters class Thr; class Con; class Tab; class Set; class Tmr; struct Par : public Opt { unsigned m_no; Con* m_con; Con& con() const { assert(m_con != 0); return *m_con; } const Tab* m_tab; const Tab& tab() const { assert(m_tab != 0); return *m_tab; } Set* m_set; Set& set() const { assert(m_set != 0); return *m_set; } Tmr* m_tmr; Tmr& tmr() const { assert(m_tmr != 0); return *m_tmr; } char m_currcase[2]; unsigned m_lno; unsigned m_slno; unsigned m_totrows; // value calculation unsigned m_range; unsigned m_pctrange; unsigned m_pctbrange; int m_bdir; bool m_noindexkeyupdate; // choice of key bool m_randomkey; // do verify after read bool m_verify; // deadlock possible bool m_deadlock; // abort percentabge unsigned m_abortpct; NdbOperation::LockMode m_lockmode; // scan options bool m_tupscan; bool m_ordered; bool m_descending; // timer location Par(const Opt& opt) : Opt(opt), m_no(0), m_con(0), m_tab(0), m_set(0), m_tmr(0), m_lno(0), m_slno(0), m_totrows(m_threads * m_rows), m_range(m_rows), m_pctrange(40), m_pctbrange(80), m_bdir(0), m_noindexkeyupdate(false), m_randomkey(false), m_verify(false), m_deadlock(false), m_abortpct(0), m_lockmode(NdbOperation::LM_Read), m_tupscan(false), m_ordered(false), m_descending(false) { m_currcase[0] = 0; } }; static bool usetable(Par par, unsigned i) { return par.m_table == 0 || strchr(par.m_table, '0' + i) != 0; } static bool useindex(Par par, unsigned i) { return par.m_index == 0 || strchr(par.m_index, '0' + i) != 0; } static unsigned thrrow(Par par, unsigned j) { return par.m_threads * j + par.m_no; } static bool isthrrow(Par par, unsigned i) { return i % par.m_threads == par.m_no; } // timer struct Tmr { void clr(); void on(); void off(unsigned cnt = 0); const char* time(); const char* pct(const Tmr& t1); const char* over(const Tmr& t1); NDB_TICKS m_on; unsigned m_ms; unsigned m_cnt; char m_time[100]; char m_text[100]; Tmr() { clr(); } }; void Tmr::clr() { m_on = m_ms = m_cnt = m_time[0] = m_text[0] = 0; } void Tmr::on() { assert(m_on == 0); m_on = NdbTick_CurrentMillisecond(); } void Tmr::off(unsigned cnt) { NDB_TICKS off = NdbTick_CurrentMillisecond(); assert(m_on != 0 && off >= m_on); m_ms += off - m_on; m_cnt += cnt; m_on = 0; } const char* Tmr::time() { if (m_cnt == 0) { sprintf(m_time, "%u ms", m_ms); } else { sprintf(m_time, "%u ms per %u ( %u ms per 1000 )", m_ms, m_cnt, (1000 * m_ms) / m_cnt); } return m_time; } const char* Tmr::pct(const Tmr& t1) { if (0 < t1.m_ms) { sprintf(m_text, "%u pct", (100 * m_ms) / t1.m_ms); } else { sprintf(m_text, "[cannot measure]"); } return m_text; } const char* Tmr::over(const Tmr& t1) { if (0 < t1.m_ms) { if (t1.m_ms <= m_ms) sprintf(m_text, "%u pct", (100 * (m_ms - t1.m_ms)) / t1.m_ms); else sprintf(m_text, "-%u pct", (100 * (t1.m_ms - m_ms)) / t1.m_ms); } else { sprintf(m_text, "[cannot measure]"); } return m_text; } // list of ints struct Lst { Lst(); unsigned m_arr[1000]; unsigned m_cnt; void push(unsigned i); unsigned cnt() const; void reset(); }; Lst::Lst() : m_cnt(0) { } void Lst::push(unsigned i) { assert(m_cnt < sizeof(m_arr)/sizeof(m_arr[0])); m_arr[m_cnt++] = i; } unsigned Lst::cnt() const { return m_cnt; } void Lst::reset() { m_cnt = 0; } // character sets static const unsigned maxcsnumber = 512; static const unsigned maxcharcount = 32; static const unsigned maxcharsize = 4; static const unsigned maxxmulsize = 8; // single mb char struct Chr { unsigned char m_bytes[maxcharsize]; unsigned char m_xbytes[maxxmulsize * maxcharsize]; unsigned m_size; Chr(); }; Chr::Chr() { memset(m_bytes, 0, sizeof(m_bytes)); memset(m_xbytes, 0, sizeof(m_xbytes)); m_size = 0; } // charset and random valid chars to use struct Chs { CHARSET_INFO* m_cs; unsigned m_xmul; Chr* m_chr; Chs(CHARSET_INFO* cs); ~Chs(); }; static NdbOut& operator<<(NdbOut& out, const Chs& chs); Chs::Chs(CHARSET_INFO* cs) : m_cs(cs) { m_xmul = m_cs->strxfrm_multiply; if (m_xmul == 0) m_xmul = 1; assert(m_xmul <= maxxmulsize); m_chr = new Chr [maxcharcount]; unsigned i = 0; unsigned miss1 = 0; unsigned miss2 = 0; unsigned miss3 = 0; unsigned miss4 = 0; while (i < maxcharcount) { unsigned char* bytes = m_chr[i].m_bytes; unsigned char* xbytes = m_chr[i].m_xbytes; unsigned& size = m_chr[i].m_size; bool ok; size = m_cs->mbminlen + urandom(m_cs->mbmaxlen - m_cs->mbminlen + 1); assert(m_cs->mbminlen <= size && size <= m_cs->mbmaxlen); // prefer longer chars if (size == m_cs->mbminlen && m_cs->mbminlen < m_cs->mbmaxlen && urandom(5) != 0) continue; for (unsigned j = 0; j < size; j++) { bytes[j] = urandom(256); } int not_used; // check wellformed const char* sbytes = (const char*)bytes; if ((*cs->cset->well_formed_len)(cs, sbytes, sbytes + size, 1, ¬_used) != size) { miss1++; continue; } // check no proper prefix wellformed ok = true; for (unsigned j = 1; j < size; j++) { if ((*cs->cset->well_formed_len)(cs, sbytes, sbytes + j, 1, ¬_used) == j) { ok = false; break; } } if (! ok) { miss2++; continue; } // normalize memset(xbytes, 0, sizeof(xbytes)); // currently returns buffer size always int xlen = (*cs->coll->strnxfrm)(cs, xbytes, m_xmul * size, bytes, size); // check we got something ok = false; for (unsigned j = 0; j < xlen; j++) { if (xbytes[j] != 0) { ok = true; break; } } if (! ok) { miss3++; continue; } // check for duplicate (before normalize) ok = true; for (unsigned j = 0; j < i; j++) { const Chr& chr = m_chr[j]; if (chr.m_size == size && memcmp(chr.m_bytes, bytes, size) == 0) { ok = false; break; } } if (! ok) { miss4++; continue; } i++; } bool disorder = true; unsigned bubbles = 0; while (disorder) { disorder = false; for (unsigned i = 1; i < maxcharcount; i++) { unsigned len = sizeof(m_chr[i].m_xbytes); if (memcmp(m_chr[i-1].m_xbytes, m_chr[i].m_xbytes, len) > 0) { Chr chr = m_chr[i]; m_chr[i] = m_chr[i-1]; m_chr[i-1] = chr; disorder = true; bubbles++; } } } LL3("inited charset " << *this << " miss=" << miss1 << "," << miss2 << "," << miss3 << "," << miss4 << " bubbles=" << bubbles); } Chs::~Chs() { delete [] m_chr; } static NdbOut& operator<<(NdbOut& out, const Chs& chs) { CHARSET_INFO* cs = chs.m_cs; out << cs->name << "[" << cs->mbminlen << "-" << cs->mbmaxlen << "," << chs.m_xmul << "]"; return out; } static Chs* cslist[maxcsnumber]; static void resetcslist() { for (unsigned i = 0; i < maxcsnumber; i++) { delete cslist[i]; cslist[i] = 0; } } static Chs* getcs(Par par) { CHARSET_INFO* cs; if (par.m_cs != 0) { cs = par.m_cs; } else { while (1) { unsigned n = urandom(maxcsnumber); cs = get_charset(n, MYF(0)); if (cs != 0) { // prefer complex charsets if (cs->mbmaxlen != 1 || urandom(5) == 0) break; } } } if (cslist[cs->number] == 0) cslist[cs->number] = new Chs(cs); return cslist[cs->number]; } // tables and indexes // Col - table column struct Col { enum Type { Unsigned = NdbDictionary::Column::Unsigned, Char = NdbDictionary::Column::Char, Varchar = NdbDictionary::Column::Varchar, Longvarchar = NdbDictionary::Column::Longvarchar }; const class Tab& m_tab; unsigned m_num; const char* m_name; bool m_pk; Type m_type; unsigned m_length; unsigned m_bytelength; // multiplied by char width unsigned m_attrsize; // base type size unsigned m_headsize; // length bytes unsigned m_bytesize; // full value size bool m_nullable; const Chs* m_chs; Col(const class Tab& tab, unsigned num, const char* name, bool pk, Type type, unsigned length, bool nullable, const Chs* chs); ~Col(); bool equal(const Col& col2) const; void wellformed(const void* addr) const; }; Col::Col(const class Tab& tab, unsigned num, const char* name, bool pk, Type type, unsigned length, bool nullable, const Chs* chs) : m_tab(tab), m_num(num), m_name(strcpy(new char [strlen(name) + 1], name)), m_pk(pk), m_type(type), m_length(length), m_bytelength(length * (chs == 0 ? 1 : chs->m_cs->mbmaxlen)), m_attrsize( type == Unsigned ? sizeof(Uint32) : type == Char ? sizeof(char) : type == Varchar ? sizeof(char) : type == Longvarchar ? sizeof(char) : ~0), m_headsize( type == Unsigned ? 0 : type == Char ? 0 : type == Varchar ? 1 : type == Longvarchar ? 2 : ~0), m_bytesize(m_headsize + m_attrsize * m_bytelength), m_nullable(nullable), m_chs(chs) { // fix long varchar if (type == Varchar && m_bytelength > 255) { m_type = Longvarchar; m_headsize += 1; m_bytesize += 1; } } Col::~Col() { delete [] m_name; } bool Col::equal(const Col& col2) const { return m_type == col2.m_type && m_length == col2.m_length && m_chs == col2.m_chs; } void Col::wellformed(const void* addr) const { switch (m_type) { case Col::Unsigned: break; case Col::Char: { CHARSET_INFO* cs = m_chs->m_cs; const char* src = (const char*)addr; unsigned len = m_bytelength; int not_used; assert((*cs->cset->well_formed_len)(cs, src, src + len, 0xffff, ¬_used) == len); } break; case Col::Varchar: { CHARSET_INFO* cs = m_chs->m_cs; const unsigned char* src = (const unsigned char*)addr; const char* ssrc = (const char*)src; unsigned len = src[0]; int not_used; assert(len <= m_bytelength); assert((*cs->cset->well_formed_len)(cs, ssrc + 1, ssrc + 1 + len, 0xffff, ¬_used) == len); } break; case Col::Longvarchar: { CHARSET_INFO* cs = m_chs->m_cs; const unsigned char* src = (const unsigned char*)addr; const char* ssrc = (const char*)src; unsigned len = src[0] + (src[1] << 8); int not_used; assert(len <= m_bytelength); assert((*cs->cset->well_formed_len)(cs, ssrc + 2, ssrc + 2 + len, 0xffff, ¬_used) == len); } break; default: assert(false); break; } } static NdbOut& operator<<(NdbOut& out, const Col& col) { out << "col[" << col.m_num << "] " << col.m_name; switch (col.m_type) { case Col::Unsigned: out << " unsigned"; break; case Col::Char: { CHARSET_INFO* cs = col.m_chs->m_cs; out << " char(" << col.m_length << "*" << cs->mbmaxlen << ";" << cs->name << ")"; } break; case Col::Varchar: { CHARSET_INFO* cs = col.m_chs->m_cs; out << " varchar(" << col.m_length << "*" << cs->mbmaxlen << ";" << cs->name << ")"; } break; case Col::Longvarchar: { CHARSET_INFO* cs = col.m_chs->m_cs; out << " longvarchar(" << col.m_length << "*" << cs->mbmaxlen << ";" << cs->name << ")"; } break; default: out << "type" << (int)col.m_type; assert(false); break; } out << (col.m_pk ? " pk" : ""); out << (col.m_nullable ? " nullable" : ""); return out; } // ICol - index column struct ICol { const class ITab& m_itab; unsigned m_num; const Col& m_col; ICol(const class ITab& itab, unsigned num, const Col& col); ~ICol(); }; ICol::ICol(const class ITab& itab, unsigned num, const Col& col) : m_itab(itab), m_num(num), m_col(col) { } ICol::~ICol() { } static NdbOut& operator<<(NdbOut& out, const ICol& icol) { out << "icol[" << icol.m_num << "] " << icol.m_col; return out; } // ITab - index struct ITab { enum Type { OrderedIndex = NdbDictionary::Index::OrderedIndex, UniqueHashIndex = NdbDictionary::Index::UniqueHashIndex }; const class Tab& m_tab; const char* m_name; Type m_type; unsigned m_icols; const ICol** m_icol; unsigned m_colmask; ITab(const class Tab& tab, const char* name, Type type, unsigned icols); ~ITab(); void icoladd(unsigned k, const ICol* icolptr); }; ITab::ITab(const class Tab& tab, const char* name, Type type, unsigned icols) : m_tab(tab), m_name(strcpy(new char [strlen(name) + 1], name)), m_type(type), m_icols(icols), m_icol(new const ICol* [icols + 1]), m_colmask(0) { for (unsigned k = 0; k <= m_icols; k++) m_icol[k] = 0; } ITab::~ITab() { delete [] m_name; for (unsigned i = 0; i < m_icols; i++) delete m_icol[i]; delete [] m_icol; } void ITab::icoladd(unsigned k, const ICol* icolptr) { assert(k == icolptr->m_num && k < m_icols && m_icol[k] == 0); m_icol[k] = icolptr; m_colmask |= (1 << icolptr->m_col.m_num); } static NdbOut& operator<<(NdbOut& out, const ITab& itab) { out << "itab " << itab.m_name << " icols=" << itab.m_icols; for (unsigned k = 0; k < itab.m_icols; k++) { const ICol& icol = *itab.m_icol[k]; out << endl << icol; } return out; } // Tab - table struct Tab { const char* m_name; unsigned m_cols; const Col** m_col; unsigned m_itabs; const ITab** m_itab; unsigned m_orderedindexes; unsigned m_hashindexes; // pk must contain an Unsigned column unsigned m_keycol; void coladd(unsigned k, Col* colptr); void itabadd(unsigned j, ITab* itab); Tab(const char* name, unsigned cols, unsigned itabs, unsigned keycol); ~Tab(); }; Tab::Tab(const char* name, unsigned cols, unsigned itabs, unsigned keycol) : m_name(strcpy(new char [strlen(name) + 1], name)), m_cols(cols), m_col(new const Col* [cols + 1]), m_itabs(itabs), m_itab(new const ITab* [itabs + 1]), m_orderedindexes(0), m_hashindexes(0), m_keycol(keycol) { for (unsigned k = 0; k <= cols; k++) m_col[k] = 0; for (unsigned j = 0; j <= itabs; j++) m_itab[j] = 0; } Tab::~Tab() { delete [] m_name; for (unsigned i = 0; i < m_cols; i++) delete m_col[i]; delete [] m_col; for (unsigned i = 0; i < m_itabs; i++) delete m_itab[i]; delete [] m_itab; } void Tab::coladd(unsigned k, Col* colptr) { assert(k == colptr->m_num && k < m_cols && m_col[k] == 0); m_col[k] = colptr; } void Tab::itabadd(unsigned j, ITab* itabptr) { assert(j < m_itabs && m_itab[j] == 0 && itabptr != 0); m_itab[j] = itabptr; if (itabptr->m_type == ITab::OrderedIndex) m_orderedindexes++; else m_hashindexes++; } static NdbOut& operator<<(NdbOut& out, const Tab& tab) { out << "tab " << tab.m_name << " cols=" << tab.m_cols; for (unsigned k = 0; k < tab.m_cols; k++) { const Col& col = *tab.m_col[k]; out << endl << col; } for (unsigned i = 0; i < tab.m_itabs; i++) { if (tab.m_itab[i] == 0) continue; const ITab& itab = *tab.m_itab[i]; out << endl << itab; } return out; } // make table structs static const Tab** tablist = 0; static unsigned tabcount = 0; static void verifytables() { for (unsigned j = 0; j < tabcount; j++) { const Tab* t = tablist[j]; if (t == 0) continue; assert(t->m_cols != 0 && t->m_col != 0); for (unsigned k = 0; k < t->m_cols; k++) { const Col* c = t->m_col[k]; assert(c != 0 && c->m_num == k); assert(! (c->m_pk && c->m_nullable)); } assert(t->m_col[t->m_cols] == 0); { assert(t->m_keycol < t->m_cols); const Col* c = t->m_col[t->m_keycol]; assert(c->m_pk && c->m_type == Col::Unsigned); } assert(t->m_itabs != 0 && t->m_itab != 0); for (unsigned i = 0; i < t->m_itabs; i++) { const ITab* x = t->m_itab[i]; if (x == 0) continue; assert(x != 0 && x->m_icols != 0 && x->m_icol != 0); for (unsigned k = 0; k < x->m_icols; k++) { const ICol* c = x->m_icol[k]; assert(c != 0 && c->m_num == k && c->m_col.m_num < t->m_cols); if (x->m_type == ITab::UniqueHashIndex) { assert(! c->m_col.m_nullable); } } } assert(t->m_itab[t->m_itabs] == 0); } } static void makebuiltintables(Par par) { LL2("makebuiltintables"); resetcslist(); tabcount = 3; if (tablist == 0) { tablist = new const Tab* [tabcount]; for (unsigned j = 0; j < tabcount; j++) { tablist[j] = 0; } } else { for (unsigned j = 0; j < tabcount; j++) { delete tablist[j]; tablist[j] = 0; } } // ti0 - basic if (usetable(par, 0)) { Tab* t = new Tab("ti0", 5, 7, 0); // name - pk - type - length - nullable - cs t->coladd(0, new Col(*t, 0, "a", 1, Col::Unsigned, 1, 0, 0)); t->coladd(1, new Col(*t, 1, "b", 0, Col::Unsigned, 1, 1, 0)); t->coladd(2, new Col(*t, 2, "c", 0, Col::Unsigned, 1, 0, 0)); t->coladd(3, new Col(*t, 3, "d", 0, Col::Unsigned, 1, 1, 0)); t->coladd(4, new Col(*t, 4, "e", 0, Col::Unsigned, 1, 0, 0)); if (useindex(par, 0)) { // a ITab* x = new ITab(*t, "ti0x0", ITab::OrderedIndex, 1); x->icoladd(0, new ICol(*x, 0, *t->m_col[0])); t->itabadd(0, x); } if (useindex(par, 1)) { // b ITab* x = new ITab(*t, "ti0x1", ITab::OrderedIndex, 1); x->icoladd(0, new ICol(*x, 0, *t->m_col[1])); t->itabadd(1, x); } if (useindex(par, 2)) { // b, c ITab* x = new ITab(*t, "ti0x2", ITab::OrderedIndex, 2); x->icoladd(0, new ICol(*x, 0, *t->m_col[1])); x->icoladd(1, new ICol(*x, 1, *t->m_col[2])); t->itabadd(2, x); } if (useindex(par, 3)) { // b, e, c, d ITab* x = new ITab(*t, "ti0x3", ITab::OrderedIndex, 4); x->icoladd(0, new ICol(*x, 0, *t->m_col[1])); x->icoladd(1, new ICol(*x, 1, *t->m_col[4])); x->icoladd(2, new ICol(*x, 2, *t->m_col[2])); x->icoladd(3, new ICol(*x, 3, *t->m_col[3])); t->itabadd(3, x); } if (useindex(par, 4)) { // a, c ITab* x = new ITab(*t, "ti0z4", ITab::UniqueHashIndex, 2); x->icoladd(0, new ICol(*x, 0, *t->m_col[0])); x->icoladd(1, new ICol(*x, 1, *t->m_col[2])); t->itabadd(4, x); } if (useindex(par, 5)) { // a, e ITab* x = new ITab(*t, "ti0z5", ITab::UniqueHashIndex, 2); x->icoladd(0, new ICol(*x, 0, *t->m_col[0])); x->icoladd(1, new ICol(*x, 1, *t->m_col[4])); t->itabadd(5, x); } tablist[0] = t; } // ti1 - simple char fields if (usetable(par, 1)) { Tab* t = new Tab("ti1", 5, 7, 1); // name - pk - type - length - nullable - cs t->coladd(0, new Col(*t, 0, "a", 0, Col::Unsigned, 1, 0, 0)); t->coladd(1, new Col(*t, 1, "b", 1, Col::Unsigned, 1, 0, 0)); t->coladd(2, new Col(*t, 2, "c", 0, Col::Varchar, 20, 0, getcs(par))); t->coladd(3, new Col(*t, 3, "d", 0, Col::Char, 5, 0, getcs(par))); t->coladd(4, new Col(*t, 4, "e", 0, Col::Longvarchar, 5, 1, getcs(par))); if (useindex(par, 0)) { // b ITab* x = new ITab(*t, "ti1x0", ITab::OrderedIndex, 1); x->icoladd(0, new ICol(*x, 0, *t->m_col[1])); t->itabadd(0, x); } if (useindex(par, 1)) { // c, a ITab* x = new ITab(*t, "ti1x1", ITab::OrderedIndex, 2); x->icoladd(0, new ICol(*x, 0, *t->m_col[2])); x->icoladd(1, new ICol(*x, 1, *t->m_col[0])); t->itabadd(1, x); } if (useindex(par, 2)) { // d ITab* x = new ITab(*t, "ti1x2", ITab::OrderedIndex, 1); x->icoladd(0, new ICol(*x, 0, *t->m_col[3])); t->itabadd(2, x); } if (useindex(par, 3)) { // e, d, c, b ITab* x = new ITab(*t, "ti1x3", ITab::OrderedIndex, 4); x->icoladd(0, new ICol(*x, 0, *t->m_col[4])); x->icoladd(1, new ICol(*x, 1, *t->m_col[3])); x->icoladd(2, new ICol(*x, 2, *t->m_col[2])); x->icoladd(3, new ICol(*x, 3, *t->m_col[1])); t->itabadd(3, x); } if (useindex(par, 4)) { // a, b ITab* x = new ITab(*t, "ti1z4", ITab::UniqueHashIndex, 2); x->icoladd(0, new ICol(*x, 0, *t->m_col[0])); x->icoladd(1, new ICol(*x, 1, *t->m_col[1])); t->itabadd(4, x); } if (useindex(par, 5)) { // b, c, d ITab* x = new ITab(*t, "ti1z5", ITab::UniqueHashIndex, 3); x->icoladd(0, new ICol(*x, 0, *t->m_col[1])); x->icoladd(1, new ICol(*x, 1, *t->m_col[2])); x->icoladd(2, new ICol(*x, 2, *t->m_col[3])); t->itabadd(5, x); } tablist[1] = t; } // ti2 - complex char fields if (usetable(par, 2)) { Tab* t = new Tab("ti2", 5, 7, 2); // name - pk - type - length - nullable - cs t->coladd(0, new Col(*t, 0, "a", 1, Col::Char, 31, 0, getcs(par))); t->coladd(1, new Col(*t, 1, "b", 0, Col::Char, 4, 1, getcs(par))); t->coladd(2, new Col(*t, 2, "c", 1, Col::Unsigned, 1, 0, 0)); t->coladd(3, new Col(*t, 3, "d", 1, Col::Varchar, 128, 0, getcs(par))); t->coladd(4, new Col(*t, 4, "e", 0, Col::Varchar, 7, 0, getcs(par))); if (useindex(par, 0)) { // a, c, d ITab* x = new ITab(*t, "ti2x0", ITab::OrderedIndex, 3); x->icoladd(0, new ICol(*x, 0, *t->m_col[0])); x->icoladd(1, new ICol(*x, 1, *t->m_col[2])); x->icoladd(2, new ICol(*x, 2, *t->m_col[3])); t->itabadd(0, x); } if (useindex(par, 1)) { // e, d, c, b, a ITab* x = new ITab(*t, "ti2x1", ITab::OrderedIndex, 5); x->icoladd(0, new ICol(*x, 0, *t->m_col[4])); x->icoladd(1, new ICol(*x, 1, *t->m_col[3])); x->icoladd(2, new ICol(*x, 2, *t->m_col[2])); x->icoladd(3, new ICol(*x, 3, *t->m_col[1])); x->icoladd(4, new ICol(*x, 4, *t->m_col[0])); t->itabadd(1, x); } if (useindex(par, 2)) { // d ITab* x = new ITab(*t, "ti2x2", ITab::OrderedIndex, 1); x->icoladd(0, new ICol(*x, 0, *t->m_col[3])); t->itabadd(2, x); } if (useindex(par, 3)) { // b ITab* x = new ITab(*t, "ti2x3", ITab::OrderedIndex, 1); x->icoladd(0, new ICol(*x, 0, *t->m_col[1])); t->itabadd(3, x); } if (useindex(par, 4)) { // a, c ITab* x = new ITab(*t, "ti2z4", ITab::UniqueHashIndex, 2); x->icoladd(0, new ICol(*x, 0, *t->m_col[0])); x->icoladd(1, new ICol(*x, 1, *t->m_col[2])); t->itabadd(4, x); } if (useindex(par, 5)) { // a, c, d, e ITab* x = new ITab(*t, "ti2z5", ITab::UniqueHashIndex, 4); x->icoladd(0, new ICol(*x, 0, *t->m_col[0])); x->icoladd(1, new ICol(*x, 1, *t->m_col[2])); x->icoladd(2, new ICol(*x, 2, *t->m_col[3])); x->icoladd(3, new ICol(*x, 3, *t->m_col[4])); t->itabadd(5, x); } tablist[2] = t; } verifytables(); } // connections static Ndb_cluster_connection* g_ncc = 0; struct Con { Ndb* m_ndb; NdbDictionary::Dictionary* m_dic; NdbConnection* m_tx; NdbOperation* m_op; NdbIndexOperation* m_indexop; NdbScanOperation* m_scanop; NdbIndexScanOperation* m_indexscanop; NdbScanFilter* m_scanfilter; enum ScanMode { ScanNo = 0, Committed, Latest, Exclusive }; ScanMode m_scanmode; enum ErrType { ErrNone = 0, ErrDeadlock, ErrNospace, ErrOther }; ErrType m_errtype; Con() : m_ndb(0), m_dic(0), m_tx(0), m_op(0), m_indexop(0), m_scanop(0), m_indexscanop(0), m_scanfilter(0), m_scanmode(ScanNo), m_errtype(ErrNone) {} ~Con() { if (m_tx != 0) closeTransaction(); } int connect(); void connect(const Con& con); void disconnect(); int startTransaction(); int getNdbOperation(const Tab& tab); int getNdbIndexOperation1(const ITab& itab, const Tab& tab); int getNdbIndexOperation(const ITab& itab, const Tab& tab); int getNdbScanOperation(const Tab& tab); int getNdbIndexScanOperation1(const ITab& itab, const Tab& tab); int getNdbIndexScanOperation(const ITab& itab, const Tab& tab); int getNdbScanFilter(); int equal(int num, const char* addr); int getValue(int num, NdbRecAttr*& rec); int setValue(int num, const char* addr); int setBound(int num, int type, const void* value); int beginFilter(int group); int endFilter(); int setFilter(int num, int cond, const void* value, unsigned len); int execute(ExecType t); int execute(ExecType t, bool& deadlock, bool& nospace); int readTuples(Par par); int readIndexTuples(Par par); int executeScan(); int nextScanResult(bool fetchAllowed); int nextScanResult(bool fetchAllowed, bool& deadlock); int updateScanTuple(Con& con2); int deleteScanTuple(Con& con2); void closeScan(); void closeTransaction(); void printerror(NdbOut& out); }; int Con::connect() { assert(m_ndb == 0); m_ndb = new Ndb(g_ncc, "TEST_DB"); CHKCON(m_ndb->init() == 0, *this); CHKCON(m_ndb->waitUntilReady(30) == 0, *this); m_tx = 0, m_op = 0; return 0; } void Con::connect(const Con& con) { assert(m_ndb == 0); m_ndb = con.m_ndb; } void Con::disconnect() { delete m_ndb; m_ndb = 0, m_dic = 0, m_tx = 0, m_op = 0; } int Con::startTransaction() { assert(m_ndb != 0); if (m_tx != 0) closeTransaction(); CHKCON((m_tx = m_ndb->startTransaction()) != 0, *this); return 0; } int Con::getNdbOperation(const Tab& tab) { assert(m_tx != 0); CHKCON((m_op = m_tx->getNdbOperation(tab.m_name)) != 0, *this); return 0; } int Con::getNdbIndexOperation1(const ITab& itab, const Tab& tab) { assert(m_tx != 0); CHKCON((m_op = m_indexop = m_tx->getNdbIndexOperation(itab.m_name, tab.m_name)) != 0, *this); return 0; } int Con::getNdbIndexOperation(const ITab& itab, const Tab& tab) { assert(m_tx != 0); unsigned tries = 0; while (1) { if (getNdbIndexOperation1(itab, tab) == 0) break; CHK(++tries < 10); NdbSleep_MilliSleep(100); } return 0; } int Con::getNdbScanOperation(const Tab& tab) { assert(m_tx != 0); CHKCON((m_op = m_scanop = m_tx->getNdbScanOperation(tab.m_name)) != 0, *this); return 0; } int Con::getNdbIndexScanOperation1(const ITab& itab, const Tab& tab) { assert(m_tx != 0); CHKCON((m_op = m_scanop = m_indexscanop = m_tx->getNdbIndexScanOperation(itab.m_name, tab.m_name)) != 0, *this); return 0; } int Con::getNdbIndexScanOperation(const ITab& itab, const Tab& tab) { assert(m_tx != 0); unsigned tries = 0; while (1) { if (getNdbIndexScanOperation1(itab, tab) == 0) break; CHK(++tries < 10); NdbSleep_MilliSleep(100); } return 0; } int Con::getNdbScanFilter() { assert(m_tx != 0 && m_scanop != 0); delete m_scanfilter; m_scanfilter = new NdbScanFilter(m_scanop); return 0; } int Con::equal(int num, const char* addr) { assert(m_tx != 0 && m_op != 0); CHKCON(m_op->equal(num, addr) == 0, *this); return 0; } int Con::getValue(int num, NdbRecAttr*& rec) { assert(m_tx != 0 && m_op != 0); CHKCON((rec = m_op->getValue(num, 0)) != 0, *this); return 0; } int Con::setValue(int num, const char* addr) { assert(m_tx != 0 && m_op != 0); CHKCON(m_op->setValue(num, addr) == 0, *this); return 0; } int Con::setBound(int num, int type, const void* value) { assert(m_tx != 0 && m_indexscanop != 0); CHKCON(m_indexscanop->setBound(num, type, value) == 0, *this); return 0; } int Con::beginFilter(int group) { assert(m_tx != 0 && m_scanfilter != 0); CHKCON(m_scanfilter->begin((NdbScanFilter::Group)group) == 0, *this); return 0; } int Con::endFilter() { assert(m_tx != 0 && m_scanfilter != 0); CHKCON(m_scanfilter->end() == 0, *this); return 0; } int Con::setFilter(int num, int cond, const void* value, unsigned len) { assert(m_tx != 0 && m_scanfilter != 0); CHKCON(m_scanfilter->cmp((NdbScanFilter::BinaryCondition)cond, num, value, len) == 0, *this); return 0; } int Con::execute(ExecType t) { assert(m_tx != 0); CHKCON(m_tx->execute(t) == 0, *this); return 0; } int Con::execute(ExecType t, bool& deadlock, bool& nospace) { int ret = execute(t); if (ret != 0 && deadlock && m_errtype == ErrDeadlock) { LL3("caught deadlock"); ret = 0; } else { deadlock = false; } if (ret != 0 && nospace && m_errtype == ErrNospace) { LL3("caught nospace"); ret = 0; } else { nospace = false; } CHK(ret == 0); return 0; } int Con::readTuples(Par par) { assert(m_tx != 0 && m_scanop != 0); int scan_flags = 0; if (par.m_tupscan) scan_flags |= NdbScanOperation::SF_TupScan; CHKCON(m_scanop->readTuples(par.m_lockmode, scan_flags, par.m_scanpar, par.m_scanbatch) == 0, *this); return 0; } int Con::readIndexTuples(Par par) { assert(m_tx != 0 && m_indexscanop != 0); int scan_flags = 0; if (par.m_ordered) scan_flags |= NdbScanOperation::SF_OrderBy; if (par.m_descending) scan_flags |= NdbScanOperation::SF_Descending; CHKCON(m_indexscanop->readTuples(par.m_lockmode, scan_flags, par.m_scanpar, par.m_scanbatch) == 0, *this); return 0; } int Con::executeScan() { CHKCON(m_tx->execute(NoCommit) == 0, *this); return 0; } int Con::nextScanResult(bool fetchAllowed) { int ret; assert(m_scanop != 0); CHKCON((ret = m_scanop->nextResult(fetchAllowed)) != -1, *this); assert(ret == 0 || ret == 1 || (! fetchAllowed && ret == 2)); return ret; } int Con::nextScanResult(bool fetchAllowed, bool& deadlock) { int ret = nextScanResult(fetchAllowed); if (ret == -1) { if (deadlock && m_errtype == ErrDeadlock) { LL3("caught deadlock"); ret = 0; } } else { deadlock = false; } CHK(ret == 0 || ret == 1 || (! fetchAllowed && ret == 2)); return ret; } int Con::updateScanTuple(Con& con2) { assert(con2.m_tx != 0); CHKCON((con2.m_op = m_scanop->updateCurrentTuple(con2.m_tx)) != 0, *this); return 0; } int Con::deleteScanTuple(Con& con2) { assert(con2.m_tx != 0); CHKCON(m_scanop->deleteCurrentTuple(con2.m_tx) == 0, *this); return 0; } void Con::closeScan() { assert(m_scanop != 0); m_scanop->close(); m_scanop = 0, m_indexscanop = 0; } void Con::closeTransaction() { assert(m_ndb != 0 && m_tx != 0); m_ndb->closeTransaction(m_tx); m_tx = 0, m_op = 0; m_scanop = 0, m_indexscanop = 0; } void Con::printerror(NdbOut& out) { m_errtype = ErrOther; unsigned any = 0; int code; int die = 0; if (m_ndb) { if ((code = m_ndb->getNdbError().code) != 0) { LL0(++any << " ndb: error " << m_ndb->getNdbError()); die += (code == g_opt.m_die); } if (m_dic && (code = m_dic->getNdbError().code) != 0) { LL0(++any << " dic: error " << m_dic->getNdbError()); die += (code == g_opt.m_die); } if (m_tx) { if ((code = m_tx->getNdbError().code) != 0) { LL0(++any << " con: error " << m_tx->getNdbError()); die += (code == g_opt.m_die); // 631 is new, occurs only on 4 db nodes, needs to be checked out if (code == 266 || code == 274 || code == 296 || code == 297 || code == 499 || code == 631) m_errtype = ErrDeadlock; if (code == 826 || code == 827 || code == 902) m_errtype = ErrNospace; } if (m_op && m_op->getNdbError().code != 0) { LL0(++any << " op : error " << m_op->getNdbError()); die += (code == g_opt.m_die); } } } if (! any) { LL0("failed but no NDB error code"); } if (die) { if (g_opt.m_core) abort(); exit(1); } } // dictionary operations static int invalidateindex(Par par, const ITab& itab) { Con& con = par.con(); const Tab& tab = par.tab(); con.m_ndb->getDictionary()->invalidateIndex(itab.m_name, tab.m_name); return 0; } static int invalidateindex(Par par) { Con& con = par.con(); const Tab& tab = par.tab(); for (unsigned i = 0; i < tab.m_itabs; i++) { if (tab.m_itab[i] == 0) continue; const ITab& itab = *tab.m_itab[i]; invalidateindex(par, itab); } return 0; } static int invalidatetable(Par par) { Con& con = par.con(); const Tab& tab = par.tab(); invalidateindex(par); con.m_ndb->getDictionary()->invalidateTable(tab.m_name); return 0; } static int droptable(Par par) { Con& con = par.con(); const Tab& tab = par.tab(); con.m_dic = con.m_ndb->getDictionary(); if (con.m_dic->getTable(tab.m_name) == 0) { // how to check for error LL4("no table " << tab.m_name); } else { LL3("drop table " << tab.m_name); CHKCON(con.m_dic->dropTable(tab.m_name) == 0, con); } con.m_dic = 0; return 0; } static int createtable(Par par) { Con& con = par.con(); const Tab& tab = par.tab(); LL3("create table " << tab.m_name); LL4(tab); NdbDictionary::Table t(tab.m_name); if (par.m_fragtype != NdbDictionary::Object::FragUndefined) { t.setFragmentType(par.m_fragtype); } if (par.m_nologging) { t.setLogging(false); } for (unsigned k = 0; k < tab.m_cols; k++) { const Col& col = *tab.m_col[k]; NdbDictionary::Column c(col.m_name); c.setType((NdbDictionary::Column::Type)col.m_type); c.setLength(col.m_bytelength); // for char NDB API uses length in bytes c.setPrimaryKey(col.m_pk); c.setNullable(col.m_nullable); if (col.m_chs != 0) c.setCharset(col.m_chs->m_cs); t.addColumn(c); } con.m_dic = con.m_ndb->getDictionary(); CHKCON(con.m_dic->createTable(t) == 0, con); con.m_dic = 0; return 0; } static int dropindex(Par par, const ITab& itab) { Con& con = par.con(); const Tab& tab = par.tab(); con.m_dic = con.m_ndb->getDictionary(); if (con.m_dic->getIndex(itab.m_name, tab.m_name) == 0) { // how to check for error LL4("no index " << itab.m_name); } else { LL3("drop index " << itab.m_name); CHKCON(con.m_dic->dropIndex(itab.m_name, tab.m_name) == 0, con); } con.m_dic = 0; return 0; } static int dropindex(Par par) { const Tab& tab = par.tab(); for (unsigned i = 0; i < tab.m_itabs; i++) { if (tab.m_itab[i] == 0) continue; const ITab& itab = *tab.m_itab[i]; CHK(dropindex(par, itab) == 0); } return 0; } static int createindex(Par par, const ITab& itab) { Con& con = par.con(); const Tab& tab = par.tab(); LL3("create index " << itab.m_name); LL4(itab); NdbDictionary::Index x(itab.m_name); x.setTable(tab.m_name); x.setType((NdbDictionary::Index::Type)itab.m_type); if (par.m_nologging || itab.m_type == ITab::OrderedIndex) { x.setLogging(false); } for (unsigned k = 0; k < itab.m_icols; k++) { const ICol& icol = *itab.m_icol[k]; const Col& col = icol.m_col; x.addColumnName(col.m_name); } con.m_dic = con.m_ndb->getDictionary(); CHKCON(con.m_dic->createIndex(x) == 0, con); con.m_dic = 0; return 0; } static int createindex(Par par) { const Tab& tab = par.tab(); for (unsigned i = 0; i < tab.m_itabs; i++) { if (tab.m_itab[i] == 0) continue; const ITab& itab = *tab.m_itab[i]; CHK(createindex(par, itab) == 0); } return 0; } // data sets // Val - typed column value struct Val { const Col& m_col; union { Uint32 m_uint32; unsigned char* m_char; unsigned char* m_varchar; unsigned char* m_longvarchar; }; Val(const Col& col); ~Val(); void copy(const Val& val2); void copy(const void* addr); const void* dataaddr() const; bool m_null; int equal(Par par) const; int equal(Par par, const ICol& icol) const; int setval(Par par) const; void calc(Par par, unsigned i); void calckey(Par par, unsigned i); void calckeychars(Par par, unsigned i, unsigned& n, unsigned char* buf); void calcnokey(Par par); void calcnokeychars(Par par, unsigned& n, unsigned char* buf); int verify(Par par, const Val& val2) const; int cmp(Par par, const Val& val2) const; int cmpchars(Par par, const unsigned char* buf1, unsigned len1, const unsigned char* buf2, unsigned len2) const; private: Val& operator=(const Val& val2); }; static NdbOut& operator<<(NdbOut& out, const Val& val); Val::Val(const Col& col) : m_col(col) { switch (col.m_type) { case Col::Unsigned: break; case Col::Char: m_char = new unsigned char [col.m_bytelength]; break; case Col::Varchar: m_varchar = new unsigned char [1 + col.m_bytelength]; break; case Col::Longvarchar: m_longvarchar = new unsigned char [2 + col.m_bytelength]; break; default: assert(false); break; } } Val::~Val() { const Col& col = m_col; switch (col.m_type) { case Col::Unsigned: break; case Col::Char: delete [] m_char; break; case Col::Varchar: delete [] m_varchar; break; case Col::Longvarchar: delete [] m_longvarchar; break; default: assert(false); break; } } void Val::copy(const Val& val2) { const Col& col = m_col; const Col& col2 = val2.m_col; assert(col.m_type == col2.m_type && col.m_length == col2.m_length); if (val2.m_null) { m_null = true; return; } copy(val2.dataaddr()); } void Val::copy(const void* addr) { const Col& col = m_col; switch (col.m_type) { case Col::Unsigned: m_uint32 = *(const Uint32*)addr; break; case Col::Char: memcpy(m_char, addr, col.m_bytelength); break; case Col::Varchar: memcpy(m_varchar, addr, 1 + col.m_bytelength); break; case Col::Longvarchar: memcpy(m_longvarchar, addr, 2 + col.m_bytelength); break; default: assert(false); break; } m_null = false; } const void* Val::dataaddr() const { const Col& col = m_col; switch (col.m_type) { case Col::Unsigned: return &m_uint32; case Col::Char: return m_char; case Col::Varchar: return m_varchar; case Col::Longvarchar: return m_longvarchar; default: break; } assert(false); return 0; } int Val::equal(Par par) const { Con& con = par.con(); const Col& col = m_col; assert(col.m_pk && ! m_null); const char* addr = (const char*)dataaddr(); LL5("equal [" << col << "] " << *this); CHK(con.equal(col.m_num, addr) == 0); return 0; } int Val::equal(Par par, const ICol& icol) const { Con& con = par.con(); assert(! m_null); const char* addr = (const char*)dataaddr(); LL5("equal [" << icol << "] " << *this); CHK(con.equal(icol.m_num, addr) == 0); return 0; } int Val::setval(Par par) const { Con& con = par.con(); const Col& col = m_col; assert(! col.m_pk); const char* addr = ! m_null ? (const char*)dataaddr() : 0; LL5("setval [" << col << "] " << *this); CHK(con.setValue(col.m_num, addr) == 0); return 0; } void Val::calc(Par par, unsigned i) { const Col& col = m_col; col.m_pk ? calckey(par, i) : calcnokey(par); if (! m_null) col.wellformed(dataaddr()); } void Val::calckey(Par par, unsigned i) { const Col& col = m_col; m_null = false; switch (col.m_type) { case Col::Unsigned: m_uint32 = i; break; case Col::Char: { const Chs* chs = col.m_chs; CHARSET_INFO* cs = chs->m_cs; unsigned n = 0; calckeychars(par, i, n, m_char); // extend by appropriate space (*cs->cset->fill)(cs, (char*)&m_char[n], col.m_bytelength - n, 0x20); } break; case Col::Varchar: { unsigned n = 0; calckeychars(par, i, n, m_varchar + 1); // set length and pad with nulls m_varchar[0] = n; memset(&m_varchar[1 + n], 0, col.m_bytelength - n); } break; case Col::Longvarchar: { unsigned n = 0; calckeychars(par, i, n, m_longvarchar + 2); // set length and pad with nulls m_longvarchar[0] = (n & 0xff); m_longvarchar[1] = (n >> 8); memset(&m_longvarchar[2 + n], 0, col.m_bytelength - n); } break; default: assert(false); break; } } void Val::calckeychars(Par par, unsigned i, unsigned& n, unsigned char* buf) { const Col& col = m_col; const Chs* chs = col.m_chs; CHARSET_INFO* cs = chs->m_cs; n = 0; unsigned len = 0; while (len < col.m_length) { if (i % (1 + n) == 0) { break; } const Chr& chr = chs->m_chr[i % maxcharcount]; assert(n + chr.m_size <= col.m_bytelength); memcpy(buf + n, chr.m_bytes, chr.m_size); n += chr.m_size; len++; } } void Val::calcnokey(Par par) { const Col& col = m_col; m_null = false; if (col.m_nullable && urandom(100) < par.m_pctnull) { m_null = true; return; } int r = irandom((par.m_pctrange * par.m_range) / 100); if (par.m_bdir != 0 && urandom(10) != 0) { if (r < 0 && par.m_bdir > 0 || r > 0 && par.m_bdir < 0) r = -r; } unsigned v = par.m_range + r; switch (col.m_type) { case Col::Unsigned: m_uint32 = v; break; case Col::Char: { const Chs* chs = col.m_chs; CHARSET_INFO* cs = chs->m_cs; unsigned n = 0; calcnokeychars(par, n, m_char); // extend by appropriate space (*cs->cset->fill)(cs, (char*)&m_char[n], col.m_bytelength - n, 0x20); } break; case Col::Varchar: { unsigned n = 0; calcnokeychars(par, n, m_varchar + 1); // set length and pad with nulls m_varchar[0] = n; memset(&m_varchar[1 + n], 0, col.m_bytelength - n); } break; case Col::Longvarchar: { unsigned n = 0; calcnokeychars(par, n, m_longvarchar + 2); // set length and pad with nulls m_longvarchar[0] = (n & 0xff); m_longvarchar[1] = (n >> 8); memset(&m_longvarchar[2 + n], 0, col.m_bytelength - n); } break; default: assert(false); break; } } void Val::calcnokeychars(Par par, unsigned& n, unsigned char* buf) { const Col& col = m_col; const Chs* chs = col.m_chs; CHARSET_INFO* cs = chs->m_cs; n = 0; unsigned len = 0; while (len < col.m_length) { if (urandom(1 + col.m_bytelength) == 0) { break; } unsigned half = maxcharcount / 2; int r = irandom((par.m_pctrange * half) / 100); if (par.m_bdir != 0 && urandom(10) != 0) { if (r < 0 && par.m_bdir > 0 || r > 0 && par.m_bdir < 0) r = -r; } unsigned i = half + r; assert(i < maxcharcount); const Chr& chr = chs->m_chr[i]; assert(n + chr.m_size <= col.m_bytelength); memcpy(buf + n, chr.m_bytes, chr.m_size); n += chr.m_size; len++; } } int Val::verify(Par par, const Val& val2) const { CHK(cmp(par, val2) == 0); return 0; } int Val::cmp(Par par, const Val& val2) const { const Col& col = m_col; const Col& col2 = val2.m_col; assert(col.equal(col2)); if (m_null || val2.m_null) { if (! m_null) return +1; if (! val2.m_null) return -1; return 0; } // verify data formats col.wellformed(dataaddr()); col.wellformed(val2.dataaddr()); // compare switch (col.m_type) { case Col::Unsigned: { if (m_uint32 < val2.m_uint32) return -1; if (m_uint32 > val2.m_uint32) return +1; return 0; } break; case Col::Char: { unsigned len = col.m_bytelength; return cmpchars(par, m_char, len, val2.m_char, len); } break; case Col::Varchar: { unsigned len1 = m_varchar[0]; unsigned len2 = val2.m_varchar[0]; return cmpchars(par, m_varchar + 1, len1, val2.m_varchar + 1, len2); } break; case Col::Longvarchar: { unsigned len1 = m_longvarchar[0] + (m_longvarchar[1] << 8); unsigned len2 = val2.m_longvarchar[0] + (val2.m_longvarchar[1] << 8); return cmpchars(par, m_longvarchar + 2, len1, val2.m_longvarchar + 2, len2); } break; default: break; } assert(false); return 0; } int Val::cmpchars(Par par, const unsigned char* buf1, unsigned len1, const unsigned char* buf2, unsigned len2) const { const Col& col = m_col; const Chs* chs = col.m_chs; CHARSET_INFO* cs = chs->m_cs; int k; if (! par.m_collsp) { unsigned char x1[maxxmulsize * 8000]; unsigned char x2[maxxmulsize * 8000]; // make strxfrm pad both to same length unsigned len = maxxmulsize * col.m_bytelength; int n1 = NdbSqlUtil::strnxfrm_bug7284(cs, x1, chs->m_xmul * len, buf1, len1); int n2 = NdbSqlUtil::strnxfrm_bug7284(cs, x2, chs->m_xmul * len, buf2, len2); assert(n1 != -1 && n1 == n2); k = memcmp(x1, x2, n1); } else { k = (*cs->coll->strnncollsp)(cs, buf1, len1, buf2, len2, false); } return k < 0 ? -1 : k > 0 ? +1 : 0; } static void printstring(NdbOut& out, const unsigned char* str, unsigned len, bool showlen) { char buf[4 * 8000]; char *p = buf; *p++ = '['; if (showlen) { sprintf(p, "%u:", len); p += strlen(p); } for (unsigned i = 0; i < len; i++) { unsigned char c = str[i]; if (c == '\\') { *p++ = '\\'; *p++ = c; } else if (0x20 <= c && c < 0x7e) { *p++ = c; } else { *p++ = '\\'; *p++ = hexstr[c >> 4]; *p++ = hexstr[c & 15]; } } *p++ = ']'; *p = 0; out << buf; } static NdbOut& operator<<(NdbOut& out, const Val& val) { const Col& col = val.m_col; if (val.m_null) { out << "NULL"; return out; } switch (col.m_type) { case Col::Unsigned: out << val.m_uint32; break; case Col::Char: { unsigned len = col.m_bytelength; printstring(out, val.m_char, len, false); } break; case Col::Varchar: { unsigned len = val.m_varchar[0]; printstring(out, val.m_varchar + 1, len, true); } break; case Col::Longvarchar: { unsigned len = val.m_longvarchar[0] + (val.m_longvarchar[1] << 8); printstring(out, val.m_longvarchar + 2, len, true); } break; default: out << "type" << col.m_type; assert(false); break; } return out; } // Row - table tuple struct Row { const Tab& m_tab; Val** m_val; bool m_exist; enum Op { NoOp = 0, ReadOp = 1, InsOp = 2, UpdOp = 4, DelOp = 8, AnyOp = 15 }; Op m_pending; Row* m_dbrow; // copy of db row before update Row(const Tab& tab); ~Row(); void copy(const Row& row2); void calc(Par par, unsigned i, unsigned mask = 0); const Row& dbrow() const; int verify(Par par, const Row& row2, bool pkonly) const; int insrow(Par par); int updrow(Par par); int updrow(Par par, const ITab& itab); int delrow(Par par); int delrow(Par par, const ITab& itab); int selrow(Par par); int selrow(Par par, const ITab& itab); int setrow(Par par); int cmp(Par par, const Row& row2) const; int cmp(Par par, const Row& row2, const ITab& itab) const; private: Row& operator=(const Row& row2); }; Row::Row(const Tab& tab) : m_tab(tab) { m_val = new Val* [tab.m_cols]; for (unsigned k = 0; k < tab.m_cols; k++) { const Col& col = *tab.m_col[k]; m_val[k] = new Val(col); } m_exist = false; m_pending = NoOp; m_dbrow = 0; } Row::~Row() { const Tab& tab = m_tab; for (unsigned k = 0; k < tab.m_cols; k++) { delete m_val[k]; } delete [] m_val; delete m_dbrow; } void Row::copy(const Row& row2) { const Tab& tab = m_tab; assert(&tab == &row2.m_tab); for (unsigned k = 0; k < tab.m_cols; k++) { Val& val = *m_val[k]; const Val& val2 = *row2.m_val[k]; val.copy(val2); } m_exist = row2.m_exist; m_pending = row2.m_pending; if (row2.m_dbrow == 0) { m_dbrow = 0; } else { assert(row2.m_dbrow->m_dbrow == 0); if (m_dbrow == 0) m_dbrow = new Row(tab); m_dbrow->copy(*row2.m_dbrow); } } void Row::calc(Par par, unsigned i, unsigned mask) { const Tab& tab = m_tab; for (unsigned k = 0; k < tab.m_cols; k++) { if (! (mask & (1 << k))) { Val& val = *m_val[k]; val.calc(par, i); } } } const Row& Row::dbrow() const { if (m_dbrow == 0) return *this; assert(m_pending == Row::UpdOp || m_pending == Row::DelOp); return *m_dbrow; } int Row::verify(Par par, const Row& row2, bool pkonly) const { const Tab& tab = m_tab; const Row& row1 = *this; assert(&row1.m_tab == &row2.m_tab && row1.m_exist && row2.m_exist); for (unsigned k = 0; k < tab.m_cols; k++) { const Col& col = row1.m_val[k]->m_col; if (! pkonly || col.m_pk) { const Val& val1 = *row1.m_val[k]; const Val& val2 = *row2.m_val[k]; CHK(val1.verify(par, val2) == 0); } } return 0; } int Row::insrow(Par par) { Con& con = par.con(); const Tab& tab = m_tab; assert(! m_exist); CHK(con.getNdbOperation(tab) == 0); CHKCON(con.m_op->insertTuple() == 0, con); Rsq rsq1(tab.m_cols); for (unsigned k = 0; k < tab.m_cols; k++) { unsigned k2 = rsq1.next(); const Val& val = *m_val[k2]; const Col& col = val.m_col; if (col.m_pk) CHK(val.equal(par) == 0); } Rsq rsq2(tab.m_cols); for (unsigned k = 0; k < tab.m_cols; k++) { unsigned k2 = rsq2.next(); const Val& val = *m_val[k2]; const Col& col = val.m_col; if (! col.m_pk) CHK(val.setval(par) == 0); } m_pending = InsOp; return 0; } int Row::updrow(Par par) { Con& con = par.con(); const Tab& tab = m_tab; assert(m_exist); CHK(con.getNdbOperation(tab) == 0); CHKCON(con.m_op->updateTuple() == 0, con); Rsq rsq1(tab.m_cols); for (unsigned k = 0; k < tab.m_cols; k++) { unsigned k2 = rsq1.next(); const Val& val = *m_val[k2]; const Col& col = val.m_col; if (col.m_pk) CHK(val.equal(par) == 0); } Rsq rsq2(tab.m_cols); for (unsigned k = 0; k < tab.m_cols; k++) { unsigned k2 = rsq2.next(); const Val& val = *m_val[k2]; const Col& col = val.m_col; if (! col.m_pk) CHK(val.setval(par) == 0); } m_pending = UpdOp; return 0; } int Row::updrow(Par par, const ITab& itab) { Con& con = par.con(); const Tab& tab = m_tab; assert(itab.m_type == ITab::UniqueHashIndex && &itab.m_tab == &tab); assert(m_exist); CHK(con.getNdbIndexOperation(itab, tab) == 0); CHKCON(con.m_op->updateTuple() == 0, con); Rsq rsq1(itab.m_icols); for (unsigned k = 0; k < itab.m_icols; k++) { unsigned k2 = rsq1.next(); const ICol& icol = *itab.m_icol[k2]; const Col& col = icol.m_col; unsigned m = col.m_num; const Val& val = *m_val[m]; CHK(val.equal(par, icol) == 0); } Rsq rsq2(tab.m_cols); for (unsigned k = 0; k < tab.m_cols; k++) { unsigned k2 = rsq2.next(); const Val& val = *m_val[k2]; const Col& col = val.m_col; if (! col.m_pk) CHK(val.setval(par) == 0); } m_pending = UpdOp; return 0; } int Row::delrow(Par par) { Con& con = par.con(); const Tab& tab = m_tab; assert(m_exist); CHK(con.getNdbOperation(m_tab) == 0); CHKCON(con.m_op->deleteTuple() == 0, con); Rsq rsq1(tab.m_cols); for (unsigned k = 0; k < tab.m_cols; k++) { unsigned k2 = rsq1.next(); const Val& val = *m_val[k2]; const Col& col = val.m_col; if (col.m_pk) CHK(val.equal(par) == 0); } m_pending = DelOp; return 0; } int Row::delrow(Par par, const ITab& itab) { Con& con = par.con(); const Tab& tab = m_tab; assert(itab.m_type == ITab::UniqueHashIndex && &itab.m_tab == &tab); assert(m_exist); CHK(con.getNdbIndexOperation(itab, tab) == 0); CHKCON(con.m_op->deleteTuple() == 0, con); Rsq rsq1(itab.m_icols); for (unsigned k = 0; k < itab.m_icols; k++) { unsigned k2 = rsq1.next(); const ICol& icol = *itab.m_icol[k2]; const Col& col = icol.m_col; unsigned m = col.m_num; const Val& val = *m_val[m]; CHK(val.equal(par, icol) == 0); } m_pending = DelOp; return 0; } int Row::selrow(Par par) { Con& con = par.con(); const Tab& tab = m_tab; CHK(con.getNdbOperation(m_tab) == 0); CHKCON(con.m_op->readTuple() == 0, con); Rsq rsq1(tab.m_cols); for (unsigned k = 0; k < tab.m_cols; k++) { unsigned k2 = rsq1.next(); const Val& val = *m_val[k2]; const Col& col = val.m_col; if (col.m_pk) CHK(val.equal(par) == 0); } return 0; } int Row::selrow(Par par, const ITab& itab) { Con& con = par.con(); const Tab& tab = m_tab; assert(itab.m_type == ITab::UniqueHashIndex && &itab.m_tab == &tab); CHK(con.getNdbIndexOperation(itab, tab) == 0); CHKCON(con.m_op->readTuple() == 0, con); Rsq rsq1(itab.m_icols); for (unsigned k = 0; k < itab.m_icols; k++) { unsigned k2 = rsq1.next(); const ICol& icol = *itab.m_icol[k2]; const Col& col = icol.m_col; unsigned m = col.m_num; const Val& val = *m_val[m]; CHK(val.equal(par, icol) == 0); } return 0; } int Row::setrow(Par par) { Con& con = par.con(); const Tab& tab = m_tab; Rsq rsq1(tab.m_cols); for (unsigned k = 0; k < tab.m_cols; k++) { unsigned k2 = rsq1.next(); const Val& val = *m_val[k2]; const Col& col = val.m_col; if (! col.m_pk) CHK(val.setval(par) == 0); } m_pending = UpdOp; return 0; } int Row::cmp(Par par, const Row& row2) const { const Tab& tab = m_tab; assert(&tab == &row2.m_tab); int c = 0; for (unsigned k = 0; k < tab.m_cols; k++) { const Val& val = *m_val[k]; const Val& val2 = *row2.m_val[k]; if ((c = val.cmp(par, val2)) != 0) break; } return c; } int Row::cmp(Par par, const Row& row2, const ITab& itab) const { const Tab& tab = m_tab; int c = 0; for (unsigned i = 0; i < itab.m_icols; i++) { const ICol& icol = *itab.m_icol[i]; const Col& col = icol.m_col; unsigned k = col.m_num; assert(k < tab.m_cols); const Val& val = *m_val[k]; const Val& val2 = *row2.m_val[k]; if ((c = val.cmp(par, val2)) != 0) break; } return c; } static NdbOut& operator<<(NdbOut& out, const Row::Op op) { if (op == Row::NoOp) out << "NoOp"; else if (op == Row::InsOp) out << "InsOp"; else if (op == Row::UpdOp) out << "UpdOp"; else if (op == Row::DelOp) out << "DelOp"; else out << op; return out; } static NdbOut& operator<<(NdbOut& out, const Row& row) { const Tab& tab = row.m_tab; for (unsigned i = 0; i < tab.m_cols; i++) { if (i > 0) out << " "; out << *row.m_val[i]; } out << " exist=" << row.m_exist; if (row.m_pending) out << " pending=" << row.m_pending; if (row.m_dbrow != 0) out << " [dbrow=" << *row.m_dbrow << "]"; return out; } static NdbOut& operator<<(NdbOut& out, const Row* rowptr) { if (rowptr == 0) out << "null"; else out << *rowptr; return out; } // Set - set of table tuples struct Set { const Tab& m_tab; unsigned m_rows; Row** m_row; unsigned* m_rowkey; // maps row number (from 0) in scan to tuple key Row* m_keyrow; NdbRecAttr** m_rec; Set(const Tab& tab, unsigned rows); ~Set(); void reset(); unsigned count() const; // old and new values bool exist(unsigned i) const; void dbsave(unsigned i); void calc(Par par, unsigned i, unsigned mask = 0); bool pending(unsigned i, unsigned mask) const; void notpending(unsigned i, ExecType et = Commit); void notpending(const Lst& lst, ExecType et = Commit); void dbdiscard(unsigned i); void dbdiscard(const Lst& lst); const Row& dbrow(unsigned i) const; // operations int insrow(Par par, unsigned i); int updrow(Par par, unsigned i); int updrow(Par par, const ITab& itab, unsigned i); int delrow(Par par, unsigned i); int delrow(Par par, const ITab& itab, unsigned i); int selrow(Par par, const Row& keyrow); int selrow(Par par, const ITab& itab, const Row& keyrow); // set and get void setkey(Par par, const Row& keyrow); void setkey(Par par, const ITab& itab, const Row& keyrow); int setrow(Par par, unsigned i); int getval(Par par); int getkey(Par par, unsigned* i); int putval(unsigned i, bool force, unsigned n = ~0); // sort rows in-place according to ordered index void sort(Par par, const ITab& itab); void sort(Par par, const ITab& itab, unsigned lo, unsigned hi); // verify int verify(Par par, const Set& set2, bool pkonly) const; int verifyorder(Par par, const ITab& itab, bool descending) const; // protect structure NdbMutex* m_mutex; void lock() const { NdbMutex_Lock(m_mutex); } void unlock() const { NdbMutex_Unlock(m_mutex); } private: Set& operator=(const Set& set2); }; Set::Set(const Tab& tab, unsigned rows) : m_tab(tab) { m_rows = rows; m_row = new Row* [m_rows]; for (unsigned i = 0; i < m_rows; i++) { // allocate on need to save space m_row[i] = 0; } m_rowkey = new unsigned [m_rows]; for (unsigned n = 0; n < m_rows; n++) { // initialize to null m_rowkey[n] = ~0; } m_keyrow = new Row(tab); m_rec = new NdbRecAttr* [tab.m_cols]; for (unsigned k = 0; k < tab.m_cols; k++) { m_rec[k] = 0; } m_mutex = NdbMutex_Create(); assert(m_mutex != 0); } Set::~Set() { for (unsigned i = 0; i < m_rows; i++) { delete m_row[i]; } delete [] m_row; delete [] m_rowkey; delete m_keyrow; delete [] m_rec; NdbMutex_Destroy(m_mutex); } void Set::reset() { for (unsigned i = 0; i < m_rows; i++) { if (m_row[i] != 0) { Row& row = *m_row[i]; row.m_exist = false; } } } unsigned Set::count() const { unsigned count = 0; for (unsigned i = 0; i < m_rows; i++) { if (m_row[i] != 0) { Row& row = *m_row[i]; if (row.m_exist) count++; } } return count; } // old and new values bool Set::exist(unsigned i) const { assert(i < m_rows); if (m_row[i] == 0) // not allocated => not exist return false; return m_row[i]->m_exist; } void Set::dbsave(unsigned i) { const Tab& tab = m_tab; assert(i < m_rows && m_row[i] != 0); Row& row = *m_row[i]; LL5("dbsave " << i << ": " << row); assert(row.m_exist && ! row.m_pending && row.m_dbrow == 0); // could swap pointers but making copy is safer Row* rowptr = new Row(tab); rowptr->copy(row); row.m_dbrow = rowptr; } void Set::calc(Par par, unsigned i, unsigned mask) { const Tab& tab = m_tab; if (m_row[i] == 0) m_row[i] = new Row(tab); Row& row = *m_row[i]; row.calc(par, i, mask); } bool Set::pending(unsigned i, unsigned mask) const { assert(i < m_rows); if (m_row[i] == 0) // not allocated => not pending return Row::NoOp; return m_row[i]->m_pending & mask; } void Set::notpending(unsigned i, ExecType et) { assert(m_row[i] != 0); Row& row = *m_row[i]; if (et == Commit) { if (row.m_pending == Row::InsOp) row.m_exist = true; if (row.m_pending == Row::DelOp) row.m_exist = false; } else { if (row.m_pending == Row::InsOp) row.m_exist = false; if (row.m_pending == Row::DelOp) row.m_exist = true; } row.m_pending = Row::NoOp; } void Set::notpending(const Lst& lst, ExecType et) { for (unsigned j = 0; j < lst.m_cnt; j++) { unsigned i = lst.m_arr[j]; notpending(i, et); } } void Set::dbdiscard(unsigned i) { assert(m_row[i] != 0); Row& row = *m_row[i]; LL5("dbdiscard " << i << ": " << row); assert(row.m_dbrow != 0); delete row.m_dbrow; row.m_dbrow = 0; } const Row& Set::dbrow(unsigned i) const { assert(m_row[i] != 0); Row& row = *m_row[i]; return row.dbrow(); } void Set::dbdiscard(const Lst& lst) { for (unsigned j = 0; j < lst.m_cnt; j++) { unsigned i = lst.m_arr[j]; dbdiscard(i); } } // operations int Set::insrow(Par par, unsigned i) { assert(m_row[i] != 0); Row& row = *m_row[i]; CHK(row.insrow(par) == 0); return 0; } int Set::updrow(Par par, unsigned i) { assert(m_row[i] != 0); Row& row = *m_row[i]; CHK(row.updrow(par) == 0); return 0; } int Set::updrow(Par par, const ITab& itab, unsigned i) { assert(m_row[i] != 0); Row& row = *m_row[i]; CHK(row.updrow(par, itab) == 0); return 0; } int Set::delrow(Par par, unsigned i) { assert(m_row[i] != 0); Row& row = *m_row[i]; CHK(row.delrow(par) == 0); return 0; } int Set::delrow(Par par, const ITab& itab, unsigned i) { assert(m_row[i] != 0); Row& row = *m_row[i]; CHK(row.delrow(par, itab) == 0); return 0; } int Set::selrow(Par par, const Row& keyrow) { Con& con = par.con(); const Tab& tab = par.tab(); setkey(par, keyrow); LL5("selrow " << tab.m_name << ": keyrow: " << keyrow); CHK(m_keyrow->selrow(par) == 0); CHK(getval(par) == 0); return 0; } int Set::selrow(Par par, const ITab& itab, const Row& keyrow) { Con& con = par.con(); setkey(par, itab, keyrow); LL5("selrow " << itab.m_name << ": keyrow: " << keyrow); CHK(m_keyrow->selrow(par, itab) == 0); CHK(getval(par) == 0); return 0; } // set and get void Set::setkey(Par par, const Row& keyrow) { const Tab& tab = m_tab; for (unsigned k = 0; k < tab.m_cols; k++) { const Col& col = *tab.m_col[k]; if (col.m_pk) { Val& val1 = *m_keyrow->m_val[k]; const Val& val2 = *keyrow.dbrow().m_val[k]; val1.copy(val2); } } } void Set::setkey(Par par, const ITab& itab, const Row& keyrow) { const Tab& tab = m_tab; for (unsigned k = 0; k < itab.m_icols; k++) { const ICol& icol = *itab.m_icol[k]; const Col& col = icol.m_col; unsigned m = col.m_num; Val& val1 = *m_keyrow->m_val[m]; const Val& val2 = *keyrow.dbrow().m_val[m]; val1.copy(val2); } } int Set::setrow(Par par, unsigned i) { Con& con = par.con(); assert(m_row[i] != 0); CHK(m_row[i]->setrow(par) == 0); return 0; } int Set::getval(Par par) { Con& con = par.con(); const Tab& tab = m_tab; Rsq rsq1(tab.m_cols); for (unsigned k = 0; k < tab.m_cols; k++) { unsigned k2 = rsq1.next(); CHK(con.getValue(k2, m_rec[k2]) == 0); } return 0; } int Set::getkey(Par par, unsigned* i) { const Tab& tab = m_tab; unsigned k = tab.m_keycol; assert(m_rec[k] != 0); const char* aRef = m_rec[k]->aRef(); Uint32 key = *(const Uint32*)aRef; LL5("getkey: " << key); CHK(key < m_rows); *i = key; return 0; } int Set::putval(unsigned i, bool force, unsigned n) { const Tab& tab = m_tab; if (m_row[i] == 0) m_row[i] = new Row(tab); Row& row = *m_row[i]; CHK(! row.m_exist || force); for (unsigned k = 0; k < tab.m_cols; k++) { Val& val = *row.m_val[k]; NdbRecAttr* rec = m_rec[k]; assert(rec != 0); if (rec->isNULL()) { val.m_null = true; continue; } const char* aRef = m_rec[k]->aRef(); val.copy(aRef); val.m_null = false; } if (! row.m_exist) row.m_exist = true; if (n != ~0) m_rowkey[n] = i; return 0; } void Set::sort(Par par, const ITab& itab) { if (m_rows != 0) sort(par, itab, 0, m_rows - 1); } void Set::sort(Par par, const ITab& itab, unsigned lo, unsigned hi) { assert(lo < m_rows && hi < m_rows && lo <= hi); Row* const p = m_row[lo]; unsigned i = lo; unsigned j = hi; while (i < j) { while (i < j && m_row[j]->cmp(par, *p, itab) >= 0) j--; if (i < j) { m_row[i] = m_row[j]; i++; } while (i < j && m_row[i]->cmp(par, *p, itab) <= 0) i++; if (i < j) { m_row[j] = m_row[i]; j--; } } m_row[i] = p; if (lo < i) sort(par, itab, lo, i - 1); if (hi > i) sort(par, itab, i + 1, hi); } int Set::verify(Par par, const Set& set2, bool pkonly) const { assert(&m_tab == &set2.m_tab && m_rows == set2.m_rows); LL4("verify set1 count=" << count() << " vs set2 count=" << set2.count()); for (unsigned i = 0; i < m_rows; i++) { bool ok = true; if (exist(i) != set2.exist(i)) { ok = false; } else if (exist(i)) { if (dbrow(i).verify(par, set2.dbrow(i), pkonly) != 0) ok = false; } if (! ok) { LL1("verify failed: key=" << i << " row1=" << m_row[i] << " row2=" << set2.m_row[i]); CHK(0 == 1); } } return 0; } int Set::verifyorder(Par par, const ITab& itab, bool descending) const { const Tab& tab = m_tab; for (unsigned n = 0; n < m_rows; n++) { unsigned i2 = m_rowkey[n]; if (i2 == ~0) break; if (n == 0) continue; unsigned i1 = m_rowkey[n - 1]; assert(i1 < m_rows && i2 < m_rows); const Row& row1 = *m_row[i1]; const Row& row2 = *m_row[i2]; assert(row1.m_exist && row2.m_exist); if (! descending) CHK(row1.cmp(par, row2, itab) <= 0); else CHK(row1.cmp(par, row2, itab) >= 0); } return 0; } static NdbOut& operator<<(NdbOut& out, const Set& set) { for (unsigned i = 0; i < set.m_rows; i++) { const Row& row = *set.m_row[i]; if (i > 0) out << endl; out << row; } return out; } // BVal - range scan bound struct BVal : public Val { const ICol& m_icol; int m_type; BVal(const ICol& icol); int setbnd(Par par) const; int setflt(Par par) const; }; BVal::BVal(const ICol& icol) : Val(icol.m_col), m_icol(icol) { } int BVal::setbnd(Par par) const { Con& con = par.con(); assert(g_compare_null || ! m_null); const char* addr = ! m_null ? (const char*)dataaddr() : 0; const ICol& icol = m_icol; CHK(con.setBound(icol.m_num, m_type, addr) == 0); return 0; } int BVal::setflt(Par par) const { static unsigned index_bound_to_filter_bound[5] = { NdbScanFilter::COND_GE, NdbScanFilter::COND_GT, NdbScanFilter::COND_LE, NdbScanFilter::COND_LT, NdbScanFilter::COND_EQ }; Con& con = par.con(); assert(g_compare_null || ! m_null); const char* addr = ! m_null ? (const char*)dataaddr() : 0; const ICol& icol = m_icol; const Col& col = icol.m_col; unsigned length = col.m_bytesize; unsigned cond = index_bound_to_filter_bound[m_type]; CHK(con.setFilter(col.m_num, cond, addr, length) == 0); return 0; } static NdbOut& operator<<(NdbOut& out, const BVal& bval) { const ICol& icol = bval.m_icol; const Col& col = icol.m_col; const Val& val = bval; out << "type=" << bval.m_type; out << " icol=" << icol.m_num; out << " col=" << col.m_num << "," << col.m_name; out << " value=" << val; return out; } // BSet - set of bounds struct BSet { const Tab& m_tab; const ITab& m_itab; unsigned m_alloc; unsigned m_bvals; BVal** m_bval; BSet(const Tab& tab, const ITab& itab, unsigned rows); ~BSet(); void reset(); void calc(Par par); void calcpk(Par par, unsigned i); int setbnd(Par par) const; int setflt(Par par) const; void filter(Par par, const Set& set, Set& set2) const; }; BSet::BSet(const Tab& tab, const ITab& itab, unsigned rows) : m_tab(tab), m_itab(itab), m_alloc(2 * itab.m_icols), m_bvals(0) { m_bval = new BVal* [m_alloc]; for (unsigned i = 0; i < m_alloc; i++) { m_bval[i] = 0; } } BSet::~BSet() { delete [] m_bval; } void BSet::reset() { while (m_bvals > 0) { unsigned i = --m_bvals; delete m_bval[i]; m_bval[i] = 0; } } void BSet::calc(Par par) { const ITab& itab = m_itab; par.m_pctrange = par.m_pctbrange; reset(); for (unsigned k = 0; k < itab.m_icols; k++) { const ICol& icol = *itab.m_icol[k]; const Col& col = icol.m_col; for (unsigned i = 0; i <= 1; i++) { if (m_bvals == 0 && urandom(100) == 0) return; if (m_bvals != 0 && urandom(3) == 0) return; assert(m_bvals < m_alloc); BVal& bval = *new BVal(icol); m_bval[m_bvals++] = &bval; bval.m_null = false; unsigned sel; do { // equality bound only on i==0 sel = urandom(5 - i); } while (strchr(par.m_bound, '0' + sel) == 0); if (sel < 2) bval.m_type = 0 | (1 << i); else if (sel < 4) bval.m_type = 1 | (1 << i); else bval.m_type = 4; if (k + 1 < itab.m_icols) bval.m_type = 4; if (! g_compare_null) par.m_pctnull = 0; if (bval.m_type == 0 || bval.m_type == 1) par.m_bdir = -1; if (bval.m_type == 2 || bval.m_type == 3) par.m_bdir = +1; do { bval.calcnokey(par); if (i == 1) { assert(m_bvals >= 2); const BVal& bv1 = *m_bval[m_bvals - 2]; const BVal& bv2 = *m_bval[m_bvals - 1]; if (bv1.cmp(par, bv2) > 0 && urandom(100) != 0) continue; } } while (0); // equality bound only once if (bval.m_type == 4) break; } } } void BSet::calcpk(Par par, unsigned i) { const ITab& itab = m_itab; reset(); for (unsigned k = 0; k < itab.m_icols; k++) { const ICol& icol = *itab.m_icol[k]; const Col& col = icol.m_col; assert(col.m_pk); assert(m_bvals < m_alloc); BVal& bval = *new BVal(icol); m_bval[m_bvals++] = &bval; bval.m_type = 4; bval.calc(par, i); } } int BSet::setbnd(Par par) const { if (m_bvals != 0) { Rsq rsq1(m_bvals); for (unsigned j = 0; j < m_bvals; j++) { unsigned j2 = rsq1.next(); const BVal& bval = *m_bval[j2]; CHK(bval.setbnd(par) == 0); } // duplicate if (urandom(5) == 0) { unsigned j3 = urandom(m_bvals); const BVal& bval = *m_bval[j3]; CHK(bval.setbnd(par) == 0); } } return 0; } int BSet::setflt(Par par) const { Con& con = par.con(); CHK(con.getNdbScanFilter() == 0); CHK(con.beginFilter(NdbScanFilter::AND) == 0); if (m_bvals != 0) { Rsq rsq1(m_bvals); for (unsigned j = 0; j < m_bvals; j++) { unsigned j2 = rsq1.next(); const BVal& bval = *m_bval[j2]; CHK(bval.setflt(par) == 0); } // duplicate if (urandom(5) == 0) { unsigned j3 = urandom(m_bvals); const BVal& bval = *m_bval[j3]; CHK(bval.setflt(par) == 0); } } CHK(con.endFilter() == 0); return 0; } void BSet::filter(Par par, const Set& set, Set& set2) const { const Tab& tab = m_tab; const ITab& itab = m_itab; assert(&tab == &set2.m_tab && set.m_rows == set2.m_rows); assert(set2.count() == 0); for (unsigned i = 0; i < set.m_rows; i++) { if (! set.exist(i)) continue; set.lock(); const Row& row = set.dbrow(i); set.unlock(); if (! g_store_null_key) { bool ok1 = false; for (unsigned k = 0; k < itab.m_icols; k++) { const ICol& icol = *itab.m_icol[k]; const Col& col = icol.m_col; const Val& val = *row.m_val[col.m_num]; if (! val.m_null) { ok1 = true; break; } } if (! ok1) continue; } bool ok2 = true; for (unsigned j = 0; j < m_bvals; j++) { const BVal& bval = *m_bval[j]; const ICol& icol = bval.m_icol; const Col& col = icol.m_col; const Val& val = *row.m_val[col.m_num]; int ret = bval.cmp(par, val); LL5("cmp: ret=" << ret << " " << bval << " vs " << val); if (bval.m_type == 0) ok2 = (ret <= 0); else if (bval.m_type == 1) ok2 = (ret < 0); else if (bval.m_type == 2) ok2 = (ret >= 0); else if (bval.m_type == 3) ok2 = (ret > 0); else if (bval.m_type == 4) ok2 = (ret == 0); else { assert(false); } if (! ok2) break; } if (! ok2) continue; if (set2.m_row[i] == 0) set2.m_row[i] = new Row(tab); Row& row2 = *set2.m_row[i]; assert(! row2.m_exist); row2.copy(row); } } static NdbOut& operator<<(NdbOut& out, const BSet& bset) { out << "bounds=" << bset.m_bvals; for (unsigned j = 0; j < bset.m_bvals; j++) { const BVal& bval = *bset.m_bval[j]; out << " [bound " << j << ": " << bval << "]"; } return out; } // pk operations static int pkinsert(Par par) { Con& con = par.con(); const Tab& tab = par.tab(); Set& set = par.set(); LL3("pkinsert " << tab.m_name); CHK(con.startTransaction() == 0); Lst lst; for (unsigned j = 0; j < par.m_rows; j++) { unsigned j2 = ! par.m_randomkey ? j : urandom(par.m_rows); unsigned i = thrrow(par, j2); set.lock(); if (set.exist(i) || set.pending(i, Row::AnyOp)) { set.unlock(); continue; } set.calc(par, i); CHK(set.insrow(par, i) == 0); set.unlock(); LL4("pkinsert " << i << ": " << *set.m_row[i]); lst.push(i); if (lst.cnt() == par.m_batch) { bool deadlock = par.m_deadlock; bool nospace = true; ExecType et = randompct(par.m_abortpct) ? Rollback : Commit; CHK(con.execute(et, deadlock, nospace) == 0); con.closeTransaction(); if (deadlock) { LL1("pkinsert: stop on deadlock [at 1]"); return 0; } if (nospace) { LL1("pkinsert: cnt=" << j << " stop on nospace"); return 0; } set.lock(); set.notpending(lst, et); set.unlock(); lst.reset(); CHK(con.startTransaction() == 0); } } if (lst.cnt() != 0) { bool deadlock = par.m_deadlock; bool nospace = true; ExecType et = randompct(par.m_abortpct) ? Rollback : Commit; CHK(con.execute(et, deadlock, nospace) == 0); con.closeTransaction(); if (deadlock) { LL1("pkinsert: stop on deadlock [at 2]"); return 0; } if (nospace) { LL1("pkinsert: end: stop on nospace"); return 0; } set.lock(); set.notpending(lst, et); set.unlock(); return 0; } con.closeTransaction(); return 0; } static int pkupdate(Par par) { Con& con = par.con(); const Tab& tab = par.tab(); Set& set = par.set(); LL3("pkupdate " << tab.m_name); CHK(con.startTransaction() == 0); Lst lst; bool deadlock = false; bool nospace = false; for (unsigned j = 0; j < par.m_rows; j++) { unsigned j2 = ! par.m_randomkey ? j : urandom(par.m_rows); unsigned i = thrrow(par, j2); set.lock(); if (! set.exist(i) || set.pending(i, Row::AnyOp)) { set.unlock(); continue; } set.dbsave(i); set.calc(par, i); CHK(set.updrow(par, i) == 0); set.unlock(); LL4("pkupdate " << i << ": " << *set.m_row[i]); lst.push(i); if (lst.cnt() == par.m_batch) { deadlock = par.m_deadlock; nospace = true; ExecType et = randompct(par.m_abortpct) ? Rollback : Commit; CHK(con.execute(et, deadlock, nospace) == 0); if (deadlock) { LL1("pkupdate: stop on deadlock [at 1]"); break; } if (nospace) { LL1("pkupdate: cnt=" << j << " stop on nospace [at 1]"); break; } con.closeTransaction(); set.lock(); set.notpending(lst, et); set.dbdiscard(lst); set.unlock(); lst.reset(); CHK(con.startTransaction() == 0); } } if (! deadlock && ! nospace && lst.cnt() != 0) { deadlock = par.m_deadlock; nospace = true; ExecType et = randompct(par.m_abortpct) ? Rollback : Commit; CHK(con.execute(et, deadlock, nospace) == 0); if (deadlock) { LL1("pkupdate: stop on deadlock [at 2]"); } else if (nospace) { LL1("pkupdate: end: stop on nospace [at 2]"); } else { set.lock(); set.notpending(lst, et); set.dbdiscard(lst); set.unlock(); } } con.closeTransaction(); return 0; } static int pkdelete(Par par) { Con& con = par.con(); const Tab& tab = par.tab(); Set& set = par.set(); LL3("pkdelete " << tab.m_name); CHK(con.startTransaction() == 0); Lst lst; bool deadlock = false; bool nospace = false; for (unsigned j = 0; j < par.m_rows; j++) { unsigned j2 = ! par.m_randomkey ? j : urandom(par.m_rows); unsigned i = thrrow(par, j2); set.lock(); if (! set.exist(i) || set.pending(i, Row::AnyOp)) { set.unlock(); continue; } CHK(set.delrow(par, i) == 0); set.unlock(); LL4("pkdelete " << i << ": " << *set.m_row[i]); lst.push(i); if (lst.cnt() == par.m_batch) { deadlock = par.m_deadlock; nospace = true; ExecType et = randompct(par.m_abortpct) ? Rollback : Commit; CHK(con.execute(et, deadlock, nospace) == 0); if (deadlock) { LL1("pkdelete: stop on deadlock [at 1]"); break; } con.closeTransaction(); set.lock(); set.notpending(lst, et); set.unlock(); lst.reset(); CHK(con.startTransaction() == 0); } } if (! deadlock && ! nospace && lst.cnt() != 0) { deadlock = par.m_deadlock; nospace = true; ExecType et = randompct(par.m_abortpct) ? Rollback : Commit; CHK(con.execute(et, deadlock, nospace) == 0); if (deadlock) { LL1("pkdelete: stop on deadlock [at 2]"); } else { set.lock(); set.notpending(lst, et); set.unlock(); } } con.closeTransaction(); return 0; } static int pkread(Par par) { Con& con = par.con(); const Tab& tab = par.tab(); Set& set = par.set(); LL3("pkread " << tab.m_name << " verify=" << par.m_verify); // expected const Set& set1 = set; Set set2(tab, set.m_rows); for (unsigned i = 0; i < set.m_rows; i++) { set.lock(); if (! set.exist(i)) { set.unlock(); continue; } set.unlock(); CHK(con.startTransaction() == 0); CHK(set2.selrow(par, *set1.m_row[i]) == 0); CHK(con.execute(Commit) == 0); unsigned i2 = (unsigned)-1; CHK(set2.getkey(par, &i2) == 0 && i == i2); CHK(set2.putval(i, false) == 0); LL4("row " << set2.count() << ": " << *set2.m_row[i]); con.closeTransaction(); } if (par.m_verify) CHK(set1.verify(par, set2, false) == 0); return 0; } static int pkreadfast(Par par, unsigned count) { Con& con = par.con(); const Tab& tab = par.tab(); const Set& set = par.set(); LL3("pkfast " << tab.m_name); Row keyrow(tab); // not batched on purpose for (unsigned j = 0; j < count; j++) { unsigned i = urandom(set.m_rows); assert(set.exist(i)); CHK(con.startTransaction() == 0); // define key keyrow.calc(par, i); CHK(keyrow.selrow(par) == 0); NdbRecAttr* rec; // get 1st column CHK(con.getValue((Uint32)0, rec) == 0); CHK(con.execute(Commit) == 0); con.closeTransaction(); } return 0; } // hash index operations static int hashindexupdate(Par par, const ITab& itab) { Con& con = par.con(); Set& set = par.set(); LL3("hashindexupdate " << itab.m_name); CHK(con.startTransaction() == 0); Lst lst; bool deadlock = false; bool nospace = false; for (unsigned j = 0; j < par.m_rows; j++) { unsigned j2 = ! par.m_randomkey ? j : urandom(par.m_rows); unsigned i = thrrow(par, j2); set.lock(); if (! set.exist(i) || set.pending(i, Row::AnyOp)) { set.unlock(); continue; } set.dbsave(i); // index key columns are not re-calculated set.calc(par, i, itab.m_colmask); CHK(set.updrow(par, itab, i) == 0); set.unlock(); LL4("hashindexupdate " << i << ": " << *set.m_row[i]); lst.push(i); if (lst.cnt() == par.m_batch) { deadlock = par.m_deadlock; CHK(con.execute(Commit, deadlock, nospace) == 0); if (deadlock) { LL1("hashindexupdate: stop on deadlock [at 1]"); break; } con.closeTransaction(); set.lock(); set.notpending(lst); set.dbdiscard(lst); set.unlock(); lst.reset(); CHK(con.startTransaction() == 0); } } if (! deadlock && lst.cnt() != 0) { deadlock = par.m_deadlock; CHK(con.execute(Commit, deadlock, nospace) == 0); if (deadlock) { LL1("hashindexupdate: stop on deadlock [at 2]"); } else { set.lock(); set.notpending(lst); set.dbdiscard(lst); set.unlock(); } } con.closeTransaction(); return 0; } static int hashindexdelete(Par par, const ITab& itab) { Con& con = par.con(); Set& set = par.set(); LL3("hashindexdelete " << itab.m_name); CHK(con.startTransaction() == 0); Lst lst; bool deadlock = false; bool nospace = false; for (unsigned j = 0; j < par.m_rows; j++) { unsigned j2 = ! par.m_randomkey ? j : urandom(par.m_rows); unsigned i = thrrow(par, j2); set.lock(); if (! set.exist(i) || set.pending(i, Row::AnyOp)) { set.unlock(); continue; } CHK(set.delrow(par, itab, i) == 0); set.unlock(); LL4("hashindexdelete " << i << ": " << *set.m_row[i]); lst.push(i); if (lst.cnt() == par.m_batch) { deadlock = par.m_deadlock; CHK(con.execute(Commit, deadlock, nospace) == 0); if (deadlock) { LL1("hashindexdelete: stop on deadlock [at 1]"); break; } con.closeTransaction(); set.lock(); set.notpending(lst); set.unlock(); lst.reset(); CHK(con.startTransaction() == 0); } } if (! deadlock && lst.cnt() != 0) { deadlock = par.m_deadlock; CHK(con.execute(Commit, deadlock, nospace) == 0); if (deadlock) { LL1("hashindexdelete: stop on deadlock [at 2]"); } else { set.lock(); set.notpending(lst); set.unlock(); } } con.closeTransaction(); return 0; } static int hashindexread(Par par, const ITab& itab) { Con& con = par.con(); const Tab& tab = par.tab(); Set& set = par.set(); LL3("hashindexread " << itab.m_name << " verify=" << par.m_verify); // expected const Set& set1 = set; Set set2(tab, set.m_rows); for (unsigned i = 0; i < set.m_rows; i++) { set.lock(); if (! set.exist(i)) { set.unlock(); continue; } set.unlock(); CHK(con.startTransaction() == 0); CHK(set2.selrow(par, itab, *set1.m_row[i]) == 0); CHK(con.execute(Commit) == 0); unsigned i2 = (unsigned)-1; CHK(set2.getkey(par, &i2) == 0 && i == i2); CHK(set2.putval(i, false) == 0); LL4("row " << set2.count() << ": " << *set2.m_row[i]); con.closeTransaction(); } if (par.m_verify) CHK(set1.verify(par, set2, false) == 0); return 0; } // scan read static int scanreadtable(Par par) { Con& con = par.con(); const Tab& tab = par.tab(); const Set& set = par.set(); // expected const Set& set1 = set; LL3("scanread " << tab.m_name << " lockmode=" << par.m_lockmode << " tupscan=" << par.m_tupscan << " expect=" << set1.count() << " verify=" << par.m_verify); Set set2(tab, set.m_rows); CHK(con.startTransaction() == 0); CHK(con.getNdbScanOperation(tab) == 0); CHK(con.readTuples(par) == 0); set2.getval(par); CHK(con.executeScan() == 0); unsigned n = 0; bool deadlock = false; while (1) { int ret; deadlock = par.m_deadlock; CHK((ret = con.nextScanResult(true, deadlock)) == 0 || ret == 1); if (ret == 1) break; if (deadlock) { LL1("scanreadtable: stop on deadlock"); break; } unsigned i = (unsigned)-1; CHK(set2.getkey(par, &i) == 0); CHK(set2.putval(i, false, n) == 0); LL4("row " << n << ": " << *set2.m_row[i]); n++; } con.closeTransaction(); if (par.m_verify) CHK(set1.verify(par, set2, false) == 0); LL3("scanread " << tab.m_name << " done rows=" << n); return 0; } static int scanreadtablefast(Par par, unsigned countcheck) { Con& con = par.con(); const Tab& tab = par.tab(); const Set& set = par.set(); LL3("scanfast " << tab.m_name); CHK(con.startTransaction() == 0); CHK(con.getNdbScanOperation(tab) == 0); CHK(con.readTuples(par) == 0); // get 1st column NdbRecAttr* rec; CHK(con.getValue((Uint32)0, rec) == 0); CHK(con.executeScan() == 0); unsigned count = 0; while (1) { int ret; CHK((ret = con.nextScanResult(true)) == 0 || ret == 1); if (ret == 1) break; count++; } con.closeTransaction(); CHK(count == countcheck); return 0; } // try to get interesting bounds static void calcscanbounds(Par par, const ITab& itab, BSet& bset, const Set& set, Set& set1) { while (true) { bset.calc(par); bset.filter(par, set, set1); unsigned n = set1.count(); // prefer proper subset if (0 < n && n < set.m_rows) break; if (urandom(5) == 0) break; set1.reset(); } } static int scanreadindex(Par par, const ITab& itab, BSet& bset, bool calc) { Con& con = par.con(); const Tab& tab = par.tab(); const Set& set = par.set(); Set set1(tab, set.m_rows); if (calc) { calcscanbounds(par, itab, bset, set, set1); } else { bset.filter(par, set, set1); } LL3("scanread " << itab.m_name << " " << bset << " lockmode=" << par.m_lockmode << " expect=" << set1.count() << " ordered=" << par.m_ordered << " descending=" << par.m_descending << " verify=" << par.m_verify); Set set2(tab, set.m_rows); CHK(con.startTransaction() == 0); CHK(con.getNdbIndexScanOperation(itab, tab) == 0); CHK(con.readIndexTuples(par) == 0); CHK(bset.setbnd(par) == 0); set2.getval(par); CHK(con.executeScan() == 0); unsigned n = 0; bool deadlock = false; while (1) { int ret; deadlock = par.m_deadlock; CHK((ret = con.nextScanResult(true, deadlock)) == 0 || ret == 1); if (ret == 1) break; if (deadlock) { LL1("scanreadindex: stop on deadlock"); break; } unsigned i = (unsigned)-1; CHK(set2.getkey(par, &i) == 0); CHK(set2.putval(i, par.m_dups, n) == 0); LL4("key " << i << " row " << n << ": " << *set2.m_row[i]); n++; } con.closeTransaction(); if (par.m_verify) { CHK(set1.verify(par, set2, false) == 0); if (par.m_ordered) CHK(set2.verifyorder(par, itab, par.m_descending) == 0); } LL3("scanread " << itab.m_name << " done rows=" << n); return 0; } static int scanreadindexfast(Par par, const ITab& itab, const BSet& bset, unsigned countcheck) { Con& con = par.con(); const Tab& tab = par.tab(); const Set& set = par.set(); LL3("scanfast " << itab.m_name << " " << bset); LL4(bset); CHK(con.startTransaction() == 0); CHK(con.getNdbIndexScanOperation(itab, tab) == 0); CHK(con.readIndexTuples(par) == 0); CHK(bset.setbnd(par) == 0); // get 1st column NdbRecAttr* rec; CHK(con.getValue((Uint32)0, rec) == 0); CHK(con.executeScan() == 0); unsigned count = 0; while (1) { int ret; CHK((ret = con.nextScanResult(true)) == 0 || ret == 1); if (ret == 1) break; count++; } con.closeTransaction(); CHK(count == countcheck); return 0; } static int scanreadfilter(Par par, const ITab& itab, BSet& bset, bool calc) { Con& con = par.con(); const Tab& tab = par.tab(); const Set& set = par.set(); Set set1(tab, set.m_rows); if (calc) { calcscanbounds(par, itab, bset, set, set1); } else { bset.filter(par, set, set1); } LL3("scanfilter " << itab.m_name << " " << bset << " lockmode=" << par.m_lockmode << " expect=" << set1.count() << " verify=" << par.m_verify); Set set2(tab, set.m_rows); CHK(con.startTransaction() == 0); CHK(con.getNdbScanOperation(tab) == 0); CHK(con.readTuples(par) == 0); CHK(bset.setflt(par) == 0); set2.getval(par); CHK(con.executeScan() == 0); unsigned n = 0; bool deadlock = false; while (1) { int ret; deadlock = par.m_deadlock; CHK((ret = con.nextScanResult(true, deadlock)) == 0 || ret == 1); if (ret == 1) break; if (deadlock) { LL1("scanfilter: stop on deadlock"); break; } unsigned i = (unsigned)-1; CHK(set2.getkey(par, &i) == 0); CHK(set2.putval(i, par.m_dups, n) == 0); LL4("key " << i << " row " << n << ": " << *set2.m_row[i]); n++; } con.closeTransaction(); if (par.m_verify) { CHK(set1.verify(par, set2, false) == 0); } LL3("scanfilter " << itab.m_name << " done rows=" << n); return 0; } static int scanreadindex(Par par, const ITab& itab) { const Tab& tab = par.tab(); for (unsigned i = 0; i < par.m_subloop; i++) { if (itab.m_type == ITab::OrderedIndex) { BSet bset(tab, itab, par.m_rows); CHK(scanreadfilter(par, itab, bset, true) == 0); CHK(scanreadindex(par, itab, bset, true) == 0); } } return 0; } static int scanreadindex(Par par) { const Tab& tab = par.tab(); for (unsigned i = 0; i < tab.m_itabs; i++) { if (tab.m_itab[i] == 0) continue; const ITab& itab = *tab.m_itab[i]; if (itab.m_type == ITab::OrderedIndex) { CHK(scanreadindex(par, itab) == 0); } else { CHK(hashindexread(par, itab) == 0); } } return 0; } static int scanreadall(Par par) { CHK(scanreadtable(par) == 0); CHK(scanreadindex(par) == 0); return 0; } // timing scans static int timescantable(Par par) { par.tmr().on(); CHK(scanreadtablefast(par, par.m_totrows) == 0); par.tmr().off(par.set().m_rows); return 0; } static int timescanpkindex(Par par) { const Tab& tab = par.tab(); const ITab& itab = *tab.m_itab[0]; // 1st index is on PK BSet bset(tab, itab, par.m_rows); par.tmr().on(); CHK(scanreadindexfast(par, itab, bset, par.m_totrows) == 0); par.tmr().off(par.set().m_rows); return 0; } static int timepkreadtable(Par par) { par.tmr().on(); unsigned count = par.m_samples; if (count == 0) count = par.m_totrows; CHK(pkreadfast(par, count) == 0); par.tmr().off(count); return 0; } static int timepkreadindex(Par par) { const Tab& tab = par.tab(); const ITab& itab = *tab.m_itab[0]; // 1st index is on PK BSet bset(tab, itab, par.m_rows); unsigned count = par.m_samples; if (count == 0) count = par.m_totrows; par.tmr().on(); for (unsigned j = 0; j < count; j++) { unsigned i = urandom(par.m_totrows); bset.calcpk(par, i); CHK(scanreadindexfast(par, itab, bset, 1) == 0); } par.tmr().off(count); return 0; } // scan update static int scanupdatetable(Par par) { Con& con = par.con(); const Tab& tab = par.tab(); Set& set = par.set(); LL3("scan update " << tab.m_name); Set set2(tab, set.m_rows); par.m_lockmode = NdbOperation::LM_Exclusive; CHK(con.startTransaction() == 0); CHK(con.getNdbScanOperation(tab) == 0); CHK(con.readTuples(par) == 0); set2.getval(par); CHK(con.executeScan() == 0); unsigned count = 0; // updating trans Con con2; con2.connect(con); CHK(con2.startTransaction() == 0); Lst lst; bool deadlock = false; bool nospace = false; while (1) { int ret; deadlock = par.m_deadlock; CHK((ret = con.nextScanResult(true, deadlock)) == 0 || ret == 1); if (ret == 1) break; if (deadlock) { LL1("scanupdatetable: stop on deadlock [at 1]"); break; } if (par.m_scanstop != 0 && urandom(par.m_scanstop) == 0) { con.closeScan(); break; } do { unsigned i = (unsigned)-1; CHK(set2.getkey(par, &i) == 0); const Row& row = *set.m_row[i]; set.lock(); if (! set.exist(i) || set.pending(i, Row::AnyOp)) { LL4("scan update " << tab.m_name << ": skip: " << row); } else { CHKTRY(set2.putval(i, false) == 0, set.unlock()); CHKTRY(con.updateScanTuple(con2) == 0, set.unlock()); Par par2 = par; par2.m_con = &con2; set.dbsave(i); set.calc(par, i); CHKTRY(set.setrow(par2, i) == 0, set.unlock()); LL4("scan update " << tab.m_name << ": " << row); lst.push(i); } set.unlock(); if (lst.cnt() == par.m_batch) { deadlock = par.m_deadlock; CHK(con2.execute(Commit, deadlock, nospace) == 0); if (deadlock) { LL1("scanupdatetable: stop on deadlock [at 2]"); goto out; } con2.closeTransaction(); set.lock(); set.notpending(lst); set.dbdiscard(lst); set.unlock(); count += lst.cnt(); lst.reset(); CHK(con2.startTransaction() == 0); } CHK((ret = con.nextScanResult(false)) == 0 || ret == 1 || ret == 2); if (ret == 2 && lst.cnt() != 0) { deadlock = par.m_deadlock; CHK(con2.execute(Commit, deadlock, nospace) == 0); if (deadlock) { LL1("scanupdatetable: stop on deadlock [at 3]"); goto out; } con2.closeTransaction(); set.lock(); set.notpending(lst); set.dbdiscard(lst); set.unlock(); count += lst.cnt(); lst.reset(); CHK(con2.startTransaction() == 0); } } while (ret == 0); if (ret == 1) break; } out: con2.closeTransaction(); LL3("scan update " << tab.m_name << " rows updated=" << count); con.closeTransaction(); return 0; } static int scanupdateindex(Par par, const ITab& itab, BSet& bset, bool calc) { Con& con = par.con(); const Tab& tab = par.tab(); Set& set = par.set(); // expected Set set1(tab, set.m_rows); if (calc) { calcscanbounds(par, itab, bset, set, set1); } else { bset.filter(par, set, set1); } LL3("scan update " << itab.m_name << " " << bset << " expect=" << set1.count() << " ordered=" << par.m_ordered << " descending=" << par.m_descending << " verify=" << par.m_verify); Set set2(tab, set.m_rows); par.m_lockmode = NdbOperation::LM_Exclusive; CHK(con.startTransaction() == 0); CHK(con.getNdbIndexScanOperation(itab, tab) == 0); CHK(con.readTuples(par) == 0); CHK(bset.setbnd(par) == 0); set2.getval(par); CHK(con.executeScan() == 0); unsigned count = 0; // updating trans Con con2; con2.connect(con); CHK(con2.startTransaction() == 0); Lst lst; bool deadlock = false; bool nospace = false; while (1) { int ret; deadlock = par.m_deadlock; CHK((ret = con.nextScanResult(true, deadlock)) == 0 || ret == 1); if (ret == 1) break; if (deadlock) { LL1("scanupdateindex: stop on deadlock [at 1]"); break; } if (par.m_scanstop != 0 && urandom(par.m_scanstop) == 0) { con.closeScan(); break; } do { unsigned i = (unsigned)-1; CHK(set2.getkey(par, &i) == 0); const Row& row = *set.m_row[i]; set.lock(); if (! set.exist(i) || set.pending(i, Row::AnyOp)) { LL4("scan update " << itab.m_name << ": skip: " << row); } else { CHKTRY(set2.putval(i, par.m_dups) == 0, set.unlock()); CHKTRY(con.updateScanTuple(con2) == 0, set.unlock()); Par par2 = par; par2.m_con = &con2; set.dbsave(i); set.calc(par, i, ! par.m_noindexkeyupdate ? 0 : itab.m_colmask); CHKTRY(set.setrow(par2, i) == 0, set.unlock()); LL4("scan update " << itab.m_name << ": " << row); lst.push(i); } set.unlock(); if (lst.cnt() == par.m_batch) { deadlock = par.m_deadlock; CHK(con2.execute(Commit, deadlock, nospace) == 0); if (deadlock) { LL1("scanupdateindex: stop on deadlock [at 2]"); goto out; } con2.closeTransaction(); LL4("scanupdateindex: committed batch [at 1]"); set.lock(); set.notpending(lst); set.dbdiscard(lst); set.unlock(); count += lst.cnt(); lst.reset(); CHK(con2.startTransaction() == 0); } CHK((ret = con.nextScanResult(false)) == 0 || ret == 1 || ret == 2); if (ret == 2 && lst.cnt() != 0) { deadlock = par.m_deadlock; CHK(con2.execute(Commit, deadlock, nospace) == 0); if (deadlock) { LL1("scanupdateindex: stop on deadlock [at 3]"); goto out; } con2.closeTransaction(); LL4("scanupdateindex: committed batch [at 2]"); set.lock(); set.notpending(lst); set.dbdiscard(lst); set.unlock(); count += lst.cnt(); lst.reset(); CHK(con2.startTransaction() == 0); } } while (ret == 0); } out: con2.closeTransaction(); if (par.m_verify) { CHK(set1.verify(par, set2, true) == 0); if (par.m_ordered) CHK(set2.verifyorder(par, itab, par.m_descending) == 0); } LL3("scan update " << itab.m_name << " rows updated=" << count); con.closeTransaction(); return 0; } static int scanupdateindex(Par par, const ITab& itab) { const Tab& tab = par.tab(); for (unsigned i = 0; i < par.m_subloop; i++) { if (itab.m_type == ITab::OrderedIndex) { BSet bset(tab, itab, par.m_rows); CHK(scanupdateindex(par, itab, bset, true) == 0); } else { CHK(hashindexupdate(par, itab) == 0); } } return 0; } static int scanupdateindex(Par par) { const Tab& tab = par.tab(); for (unsigned i = 0; i < tab.m_itabs; i++) { if (tab.m_itab[i] == 0) continue; const ITab& itab = *tab.m_itab[i]; CHK(scanupdateindex(par, itab) == 0); } return 0; } static int scanupdateall(Par par) { CHK(scanupdatetable(par) == 0); CHK(scanupdateindex(par) == 0); return 0; } // medium level routines static int readverifyfull(Par par) { if (par.m_noverify) return 0; par.m_verify = true; if (par.m_abortpct != 0) { LL2("skip verify in this version"); // implement in 5.0 version par.m_verify = false; } par.m_lockmode = NdbOperation::LM_CommittedRead; const Tab& tab = par.tab(); if (par.m_no == 0) { // thread 0 scans table CHK(scanreadtable(par) == 0); // once more via tup scan par.m_tupscan = true; CHK(scanreadtable(par) == 0); } // each thread scans different indexes for (unsigned i = 0; i < tab.m_itabs; i++) { if (i % par.m_threads != par.m_no) continue; if (tab.m_itab[i] == 0) continue; const ITab& itab = *tab.m_itab[i]; if (itab.m_type == ITab::OrderedIndex) { BSet bset(tab, itab, par.m_rows); CHK(scanreadindex(par, itab, bset, false) == 0); } else { CHK(hashindexread(par, itab) == 0); } } return 0; } static int readverifyindex(Par par) { if (par.m_noverify) return 0; par.m_verify = true; par.m_lockmode = NdbOperation::LM_CommittedRead; unsigned sel = urandom(10); if (sel < 9) { par.m_ordered = true; par.m_descending = (sel < 5); } CHK(scanreadindex(par) == 0); return 0; } static int pkops(Par par) { const Tab& tab = par.tab(); par.m_randomkey = true; for (unsigned i = 0; i < par.m_subloop; i++) { unsigned j = 0; while (j < tab.m_itabs) { if (tab.m_itab[j] != 0) { const ITab& itab = *tab.m_itab[j]; if (itab.m_type == ITab::UniqueHashIndex && urandom(5) == 0) break; } j++; } unsigned sel = urandom(10); if (par.m_slno % 2 == 0) { // favor insert if (sel < 8) { CHK(pkinsert(par) == 0); } else if (sel < 9) { if (j == tab.m_itabs) CHK(pkupdate(par) == 0); else { const ITab& itab = *tab.m_itab[j]; CHK(hashindexupdate(par, itab) == 0); } } else { if (j == tab.m_itabs) CHK(pkdelete(par) == 0); else { const ITab& itab = *tab.m_itab[j]; CHK(hashindexdelete(par, itab) == 0); } } } else { // favor delete if (sel < 1) { CHK(pkinsert(par) == 0); } else if (sel < 2) { if (j == tab.m_itabs) CHK(pkupdate(par) == 0); else { const ITab& itab = *tab.m_itab[j]; CHK(hashindexupdate(par, itab) == 0); } } else { if (j == tab.m_itabs) CHK(pkdelete(par) == 0); else { const ITab& itab = *tab.m_itab[j]; CHK(hashindexdelete(par, itab) == 0); } } } } return 0; } static int pkupdatescanread(Par par) { par.m_dups = true; par.m_deadlock = true; unsigned sel = urandom(10); if (sel < 5) { CHK(pkupdate(par) == 0); } else if (sel < 6) { par.m_verify = false; CHK(scanreadtable(par) == 0); } else { par.m_verify = false; if (sel < 8) { par.m_ordered = true; par.m_descending = (sel < 7); } CHK(scanreadindex(par) == 0); } return 0; } static int mixedoperations(Par par) { par.m_dups = true; par.m_deadlock = true; par.m_scanstop = par.m_totrows; // randomly close scans unsigned sel = urandom(10); if (sel < 2) { CHK(pkdelete(par) == 0); } else if (sel < 4) { CHK(pkupdate(par) == 0); } else if (sel < 6) { CHK(scanupdatetable(par) == 0); } else { if (sel < 8) { par.m_ordered = true; par.m_descending = (sel < 7); } CHK(scanupdateindex(par) == 0); } return 0; } static int parallelorderedupdate(Par par) { const Tab& tab = par.tab(); unsigned k = 0; for (unsigned i = 0; i < tab.m_itabs; i++) { if (tab.m_itab[i] == 0) continue; const ITab& itab = *tab.m_itab[i]; if (itab.m_type != ITab::OrderedIndex) continue; // cannot sync threads yet except via subloop if (k++ == par.m_slno % tab.m_orderedindexes) { LL3("parallelorderedupdate: " << itab.m_name); par.m_noindexkeyupdate = true; par.m_ordered = true; par.m_descending = (par.m_slno != 0); par.m_verify = true; BSet bset(tab, itab, par.m_rows); // empty bounds // prefer empty bounds unsigned sel = urandom(10); CHK(scanupdateindex(par, itab, bset, sel < 2) == 0); } } return 0; } static int pkupdateindexbuild(Par par) { if (par.m_no == 0) { CHK(createindex(par) == 0); } else { par.m_randomkey = true; CHK(pkupdate(par) == 0); } return 0; } // threads typedef int (*TFunc)(Par par); enum TMode { ST = 1, MT = 2 }; extern "C" { static void* runthread(void* arg); } struct Thr { enum State { Wait, Start, Stop, Stopped, Exit }; State m_state; Par m_par; Uint64 m_id; NdbThread* m_thread; NdbMutex* m_mutex; NdbCondition* m_cond; TFunc m_func; int m_ret; void* m_status; Thr(Par par, unsigned n); ~Thr(); int run(); void start(); void stop(); void stopped(); void exit(); // 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_thread, &m_status); m_thread = 0; } }; Thr::Thr(Par par, unsigned n) : m_state(Wait), m_par(par), m_id(0), m_thread(0), m_mutex(0), m_cond(0), m_func(0), m_ret(0), m_status(0) { m_par.m_no = n; char buf[10]; sprintf(buf, "thr%03u", par.m_no); const char* name = strcpy(new char[10], buf); // mutex m_mutex = NdbMutex_Create(); m_cond = NdbCondition_Create(); assert(m_mutex != 0 && m_cond != 0); // run const unsigned stacksize = 256 * 1024; const NDB_THREAD_PRIO prio = NDB_THREAD_PRIO_LOW; m_thread = NdbThread_Create(runthread, (void**)this, stacksize, name, prio); } Thr::~Thr() { if (m_thread != 0) { NdbThread_Destroy(&m_thread); m_thread = 0; } if (m_cond != 0) { NdbCondition_Destroy(m_cond); m_cond = 0; } if (m_mutex != 0) { NdbMutex_Destroy(m_mutex); m_mutex = 0; } } static void* runthread(void* arg) { Thr& thr = *(Thr*)arg; thr.m_id = (Uint64)pthread_self(); if (thr.run() < 0) { LL1("exit on error"); } else { LL4("exit ok"); } return 0; } int Thr::run() { LL4("run"); Con con; CHK(con.connect() == 0); m_par.m_con = &con; LL4("connected"); while (1) { lock(); while (m_state != Start && m_state != Exit) { LL4("wait"); wait(); } if (m_state == Exit) { LL4("exit"); unlock(); break; } LL4("start"); assert(m_state == Start); m_ret = (*m_func)(m_par); m_state = Stopped; LL4("stop"); signal(); unlock(); CHK(m_ret == 0); } con.disconnect(); return 0; } void Thr::start() { lock(); m_state = Start; signal(); unlock(); } void Thr::stop() { lock(); m_state = Stop; signal(); unlock(); } void Thr::stopped() { lock(); while (m_state != Stopped) wait(); m_state = Wait; unlock(); } void Thr::exit() { lock(); m_state = Exit; signal(); unlock(); } // test run static Thr** g_thrlist = 0; static unsigned getthrno() { if (g_thrlist != 0) { Uint64 id = (Uint64)pthread_self(); for (unsigned n = 0; n < g_opt.m_threads; n++) { if (g_thrlist[n] != 0) { const Thr& thr = *g_thrlist[n]; if (thr.m_id == id) return thr.m_par.m_no; } } } return (unsigned)-1; } static int runstep(Par par, const char* fname, TFunc func, unsigned mode) { LL2("step: " << fname); const int threads = (mode & ST ? 1 : par.m_threads); int n; for (n = 0; n < threads; n++) { LL4("start " << n); Thr& thr = *g_thrlist[n]; Par oldpar = thr.m_par; // update parameters thr.m_par = par; thr.m_par.m_no = oldpar.m_no; thr.m_par.m_con = oldpar.m_con; thr.m_func = func; thr.start(); } unsigned errs = 0; for (n = threads - 1; n >= 0; n--) { LL4("stop " << n); Thr& thr = *g_thrlist[n]; thr.stopped(); if (thr.m_ret != 0) errs++; } CHK(errs == 0); return 0; } #define RUNSTEP(par, func, mode) \ CHK(runstep(par, #func, func, mode) == 0) #define SUBLOOP(par) \ "subloop: " << par.m_lno << "/" << par.m_currcase << "/" << \ par.m_tab->m_name << "/" << par.m_slno static int tbuild(Par par) { RUNSTEP(par, droptable, ST); RUNSTEP(par, createtable, ST); RUNSTEP(par, invalidatetable, MT); for (par.m_slno = 0; par.m_slno < par.m_subloop; par.m_slno++) { LL1(SUBLOOP(par)); if (par.m_slno % 3 == 0) { RUNSTEP(par, createindex, ST); RUNSTEP(par, invalidateindex, MT); RUNSTEP(par, pkinsert, MT); RUNSTEP(par, pkupdate, MT); } else if (par.m_slno % 3 == 1) { RUNSTEP(par, pkinsert, MT); RUNSTEP(par, createindex, ST); RUNSTEP(par, invalidateindex, MT); RUNSTEP(par, pkupdate, MT); } else { RUNSTEP(par, pkinsert, MT); RUNSTEP(par, pkupdate, MT); RUNSTEP(par, createindex, ST); RUNSTEP(par, invalidateindex, MT); } RUNSTEP(par, readverifyfull, MT); // leave last one if (par.m_slno + 1 < par.m_subloop) { RUNSTEP(par, pkdelete, MT); RUNSTEP(par, readverifyfull, MT); RUNSTEP(par, dropindex, ST); } } return 0; } static int tindexscan(Par par) { RUNSTEP(par, droptable, ST); RUNSTEP(par, createtable, ST); RUNSTEP(par, invalidatetable, MT); RUNSTEP(par, createindex, ST); RUNSTEP(par, invalidateindex, MT); RUNSTEP(par, pkinsert, MT); RUNSTEP(par, readverifyfull, MT); for (par.m_slno = 0; par.m_slno < par.m_subloop; par.m_slno++) { LL1(SUBLOOP(par)); RUNSTEP(par, readverifyindex, MT); } return 0; } static int tpkops(Par par) { RUNSTEP(par, droptable, ST); RUNSTEP(par, createtable, ST); RUNSTEP(par, invalidatetable, MT); RUNSTEP(par, createindex, ST); RUNSTEP(par, invalidateindex, MT); for (par.m_slno = 0; par.m_slno < par.m_subloop; par.m_slno++) { LL1(SUBLOOP(par)); RUNSTEP(par, pkops, MT); LL2("rows=" << par.set().count()); RUNSTEP(par, readverifyfull, MT); } return 0; } static int tpkopsread(Par par) { RUNSTEP(par, droptable, ST); RUNSTEP(par, createtable, ST); RUNSTEP(par, invalidatetable, MT); RUNSTEP(par, pkinsert, MT); RUNSTEP(par, createindex, ST); RUNSTEP(par, invalidateindex, MT); RUNSTEP(par, readverifyfull, MT); for (par.m_slno = 0; par.m_slno < par.m_subloop; par.m_slno++) { LL1(SUBLOOP(par)); RUNSTEP(par, pkupdatescanread, MT); RUNSTEP(par, readverifyfull, MT); } RUNSTEP(par, pkdelete, MT); RUNSTEP(par, readverifyfull, MT); return 0; } static int tmixedops(Par par) { RUNSTEP(par, droptable, ST); RUNSTEP(par, createtable, ST); RUNSTEP(par, invalidatetable, MT); RUNSTEP(par, pkinsert, MT); RUNSTEP(par, createindex, ST); RUNSTEP(par, invalidateindex, MT); RUNSTEP(par, readverifyfull, MT); for (par.m_slno = 0; par.m_slno < par.m_subloop; par.m_slno++) { LL1(SUBLOOP(par)); RUNSTEP(par, mixedoperations, MT); RUNSTEP(par, readverifyfull, MT); } return 0; } static int tbusybuild(Par par) { RUNSTEP(par, droptable, ST); RUNSTEP(par, createtable, ST); RUNSTEP(par, invalidatetable, MT); RUNSTEP(par, pkinsert, MT); for (par.m_slno = 0; par.m_slno < par.m_subloop; par.m_slno++) { LL1(SUBLOOP(par)); RUNSTEP(par, pkupdateindexbuild, MT); RUNSTEP(par, invalidateindex, MT); RUNSTEP(par, readverifyfull, MT); RUNSTEP(par, dropindex, ST); } return 0; } static int trollback(Par par) { par.m_abortpct = 50; RUNSTEP(par, droptable, ST); RUNSTEP(par, createtable, ST); RUNSTEP(par, invalidatetable, MT); RUNSTEP(par, pkinsert, MT); RUNSTEP(par, createindex, ST); RUNSTEP(par, invalidateindex, MT); RUNSTEP(par, readverifyfull, MT); for (par.m_slno = 0; par.m_slno < par.m_subloop; par.m_slno++) { LL1(SUBLOOP(par)); RUNSTEP(par, mixedoperations, MT); RUNSTEP(par, readverifyfull, MT); } return 0; } static int tparupdate(Par par) { RUNSTEP(par, droptable, ST); RUNSTEP(par, createtable, ST); RUNSTEP(par, invalidatetable, MT); RUNSTEP(par, pkinsert, MT); RUNSTEP(par, createindex, ST); RUNSTEP(par, invalidateindex, MT); RUNSTEP(par, readverifyfull, MT); for (par.m_slno = 0; par.m_slno < par.m_subloop; par.m_slno++) { LL1(SUBLOOP(par)); RUNSTEP(par, parallelorderedupdate, MT); RUNSTEP(par, readverifyfull, MT); } return 0; } static int ttimebuild(Par par) { Tmr t1; RUNSTEP(par, droptable, ST); RUNSTEP(par, createtable, ST); RUNSTEP(par, invalidatetable, MT); for (par.m_slno = 0; par.m_slno < par.m_subloop; par.m_slno++) { LL1(SUBLOOP(par)); RUNSTEP(par, pkinsert, MT); t1.on(); RUNSTEP(par, createindex, ST); t1.off(par.m_totrows); RUNSTEP(par, invalidateindex, MT); RUNSTEP(par, dropindex, ST); } LL1("build index - " << t1.time()); return 0; } static int ttimemaint(Par par) { Tmr t1, t2; RUNSTEP(par, droptable, ST); RUNSTEP(par, createtable, ST); RUNSTEP(par, invalidatetable, MT); for (par.m_slno = 0; par.m_slno < par.m_subloop; par.m_slno++) { LL1(SUBLOOP(par)); RUNSTEP(par, pkinsert, MT); t1.on(); RUNSTEP(par, pkupdate, MT); t1.off(par.m_totrows); RUNSTEP(par, createindex, ST); RUNSTEP(par, invalidateindex, MT); t2.on(); RUNSTEP(par, pkupdate, MT); t2.off(par.m_totrows); RUNSTEP(par, dropindex, ST); } LL1("update - " << t1.time()); LL1("update indexed - " << t2.time()); LL1("overhead - " << t2.over(t1)); return 0; } static int ttimescan(Par par) { if (par.tab().m_itab[0] == 0) { LL1("ttimescan - no index 0, skipped"); return 0; } Tmr t1, t2; RUNSTEP(par, droptable, ST); RUNSTEP(par, createtable, ST); RUNSTEP(par, invalidatetable, MT); for (par.m_slno = 0; par.m_slno < par.m_subloop; par.m_slno++) { LL1(SUBLOOP(par)); RUNSTEP(par, pkinsert, MT); RUNSTEP(par, createindex, ST); par.m_tmr = &t1; RUNSTEP(par, timescantable, ST); par.m_tmr = &t2; RUNSTEP(par, timescanpkindex, ST); RUNSTEP(par, dropindex, ST); } LL1("full scan table - " << t1.time()); LL1("full scan PK index - " << t2.time()); LL1("overhead - " << t2.over(t1)); return 0; } static int ttimepkread(Par par) { if (par.tab().m_itab[0] == 0) { LL1("ttimescan - no index 0, skipped"); return 0; } Tmr t1, t2; RUNSTEP(par, droptable, ST); RUNSTEP(par, createtable, ST); RUNSTEP(par, invalidatetable, MT); for (par.m_slno = 0; par.m_slno < par.m_subloop; par.m_slno++) { LL1(SUBLOOP(par)); RUNSTEP(par, pkinsert, MT); RUNSTEP(par, createindex, ST); par.m_tmr = &t1; RUNSTEP(par, timepkreadtable, ST); par.m_tmr = &t2; RUNSTEP(par, timepkreadindex, ST); RUNSTEP(par, dropindex, ST); } LL1("pk read table - " << t1.time()); LL1("pk read PK index - " << t2.time()); LL1("overhead - " << t2.over(t1)); return 0; } static int tdrop(Par par) { RUNSTEP(par, droptable, ST); return 0; } struct TCase { const char* m_name; TFunc m_func; const char* m_desc; TCase(const char* name, TFunc func, const char* desc) : m_name(name), m_func(func), m_desc(desc) { } }; static const TCase tcaselist[] = { TCase("a", tbuild, "index build"), TCase("b", tindexscan, "index scans"), TCase("c", tpkops, "pk operations"), TCase("d", tpkopsread, "pk operations and scan reads"), TCase("e", tmixedops, "pk operations and scan operations"), TCase("f", tbusybuild, "pk operations and index build"), TCase("g", trollback, "operations with random rollbacks"), TCase("h", tparupdate, "parallel ordered update (bug20446)"), TCase("t", ttimebuild, "time index build"), TCase("u", ttimemaint, "time index maintenance"), TCase("v", ttimescan, "time full scan table vs index on pk"), TCase("w", ttimepkread, "time pk read table vs index on pk"), TCase("z", tdrop, "drop test tables") }; static const unsigned tcasecount = sizeof(tcaselist) / sizeof(tcaselist[0]); static void printcases() { ndbout << "test cases:" << endl; for (unsigned i = 0; i < tcasecount; i++) { const TCase& tcase = tcaselist[i]; ndbout << " " << tcase.m_name << " - " << tcase.m_desc << endl; } } static void printtables() { Par par(g_opt); makebuiltintables(par); ndbout << "tables and indexes (x=ordered z=hash x0=on pk):" << endl; for (unsigned j = 0; j < tabcount; j++) { if (tablist[j] == 0) continue; const Tab& tab = *tablist[j]; const char* tname = tab.m_name; ndbout << " " << tname; for (unsigned i = 0; i < tab.m_itabs; i++) { if (tab.m_itab[i] == 0) continue; const ITab& itab = *tab.m_itab[i]; const char* iname = itab.m_name; if (strncmp(tname, iname, strlen(tname)) == 0) iname += strlen(tname); ndbout << " " << iname; ndbout << "("; for (unsigned k = 0; k < itab.m_icols; k++) { if (k != 0) ndbout << ","; const ICol& icol = *itab.m_icol[k]; const Col& col = icol.m_col; ndbout << col.m_name; } ndbout << ")"; } ndbout << endl; } } static int runtest(Par par) { if (par.m_seed == -1) { // good enough for daily run unsigned short seed = (unsigned short)getpid(); LL0("random seed: " << seed); srandom((unsigned)seed); } else if (par.m_seed != 0) { LL0("random seed: " << par.m_seed); srandom(par.m_seed); } else { LL0("random seed: loop number"); } // cs assert(par.m_csname != 0); if (strcmp(par.m_csname, "random") != 0) { CHARSET_INFO* cs; CHK((cs = get_charset_by_name(par.m_csname, MYF(0))) != 0 || (cs = get_charset_by_csname(par.m_csname, MY_CS_PRIMARY, MYF(0))) != 0); par.m_cs = cs; } // con Con con; CHK(con.connect() == 0); par.m_con = &con; // threads g_thrlist = new Thr* [par.m_threads]; unsigned n; for (n = 0; n < par.m_threads; n++) { g_thrlist[n] = 0; } for (n = 0; n < par.m_threads; n++) { g_thrlist[n] = new Thr(par, n); Thr& thr = *g_thrlist[n]; assert(thr.m_thread != 0); } for (par.m_lno = 0; par.m_loop == 0 || par.m_lno < par.m_loop; par.m_lno++) { LL1("loop: " << par.m_lno); if (par.m_seed == 0) { LL1("random seed: " << par.m_lno); srandom(par.m_lno); } for (unsigned i = 0; i < tcasecount; i++) { const TCase& tcase = tcaselist[i]; if (par.m_case != 0 && strchr(par.m_case, tcase.m_name[0]) == 0) continue; sprintf(par.m_currcase, "%c", tcase.m_name[0]); makebuiltintables(par); LL1("case: " << par.m_lno << "/" << tcase.m_name << " - " << tcase.m_desc); for (unsigned j = 0; j < tabcount; j++) { if (tablist[j] == 0) continue; const Tab& tab = *tablist[j]; par.m_tab = &tab; par.m_set = new Set(tab, par.m_totrows); LL1("table: " << par.m_lno << "/" << tcase.m_name << "/" << tab.m_name); CHK(tcase.m_func(par) == 0); delete par.m_set; par.m_set = 0; } } } for (n = 0; n < par.m_threads; n++) { Thr& thr = *g_thrlist[n]; thr.exit(); } for (n = 0; n < par.m_threads; n++) { Thr& thr = *g_thrlist[n]; thr.join(); delete &thr; } delete [] g_thrlist; g_thrlist = 0; con.disconnect(); return 0; } static const char* g_progname = "testOIBasic"; int main(int argc, char** argv) { ndb_init(); unsigned i; ndbout << g_progname; for (i = 1; i < argc; i++) ndbout << " " << argv[i]; ndbout << endl; ndbout_mutex = NdbMutex_Create(); while (++argv, --argc > 0) { const char* arg = argv[0]; if (*arg != '-') { ndbout << "testOIBasic: unknown argument " << arg; goto usage; } if (strcmp(arg, "-batch") == 0) { if (++argv, --argc > 0) { g_opt.m_batch = atoi(argv[0]); continue; } } if (strcmp(arg, "-bound") == 0) { if (++argv, --argc > 0) { const char* p = argv[0]; if (strlen(p) != 0 && strlen(p) == strspn(p, "01234")) { g_opt.m_bound = strdup(p); continue; } } } if (strcmp(arg, "-case") == 0) { if (++argv, --argc > 0) { g_opt.m_case = strdup(argv[0]); continue; } } if (strcmp(arg, "-collsp") == 0) { g_opt.m_collsp = true; continue; } if (strcmp(arg, "-core") == 0) { g_opt.m_core = true; continue; } if (strcmp(arg, "-csname") == 0) { if (++argv, --argc > 0) { g_opt.m_csname = strdup(argv[0]); continue; } } if (strcmp(arg, "-die") == 0) { if (++argv, --argc > 0) { g_opt.m_die = atoi(argv[0]); continue; } } if (strcmp(arg, "-dups") == 0) { g_opt.m_dups = true; continue; } if (strcmp(arg, "-fragtype") == 0) { if (++argv, --argc > 0) { if (strcmp(argv[0], "single") == 0) { g_opt.m_fragtype = NdbDictionary::Object::FragSingle; continue; } if (strcmp(argv[0], "small") == 0) { g_opt.m_fragtype = NdbDictionary::Object::FragAllSmall; continue; } if (strcmp(argv[0], "medium") == 0) { g_opt.m_fragtype = NdbDictionary::Object::FragAllMedium; continue; } if (strcmp(argv[0], "large") == 0) { g_opt.m_fragtype = NdbDictionary::Object::FragAllLarge; continue; } } } if (strcmp(arg, "-index") == 0) { if (++argv, --argc > 0) { g_opt.m_index = strdup(argv[0]); continue; } } if (strcmp(arg, "-loop") == 0) { if (++argv, --argc > 0) { g_opt.m_loop = atoi(argv[0]); continue; } } if (strcmp(arg, "-nologging") == 0) { g_opt.m_nologging = true; continue; } if (strcmp(arg, "-noverify") == 0) { g_opt.m_noverify = true; continue; } if (strcmp(arg, "-pctnull") == 0) { if (++argv, --argc > 0) { g_opt.m_pctnull = atoi(argv[0]); continue; } } if (strcmp(arg, "-rows") == 0) { if (++argv, --argc > 0) { g_opt.m_rows = atoi(argv[0]); continue; } } if (strcmp(arg, "-samples") == 0) { if (++argv, --argc > 0) { g_opt.m_samples = atoi(argv[0]); continue; } } if (strcmp(arg, "-scanbatch") == 0) { if (++argv, --argc > 0) { g_opt.m_scanbatch = atoi(argv[0]); continue; } } if (strcmp(arg, "-scanpar") == 0) { if (++argv, --argc > 0) { g_opt.m_scanpar = atoi(argv[0]); continue; } } if (strcmp(arg, "-seed") == 0) { if (++argv, --argc > 0) { g_opt.m_seed = atoi(argv[0]); continue; } } if (strcmp(arg, "-subloop") == 0) { if (++argv, --argc > 0) { g_opt.m_subloop = atoi(argv[0]); continue; } } if (strcmp(arg, "-table") == 0) { if (++argv, --argc > 0) { g_opt.m_table = strdup(argv[0]); continue; } } if (strcmp(arg, "-threads") == 0) { if (++argv, --argc > 0) { g_opt.m_threads = atoi(argv[0]); if (1 <= g_opt.m_threads) continue; } } if (strcmp(arg, "-v") == 0) { if (++argv, --argc > 0) { g_opt.m_v = atoi(argv[0]); continue; } } if (strncmp(arg, "-v", 2) == 0 && isdigit(arg[2])) { g_opt.m_v = atoi(&arg[2]); continue; } if (strcmp(arg, "-h") == 0 || strcmp(arg, "-help") == 0) { printhelp(); goto wrongargs; } ndbout << "testOIBasic: bad or unknown option " << arg; goto usage; } { Par par(g_opt); g_ncc = new Ndb_cluster_connection(); if (g_ncc->connect(30) != 0 || runtest(par) < 0) goto failed; delete g_ncc; g_ncc = 0; } ok: return NDBT_ProgramExit(NDBT_OK); failed: return NDBT_ProgramExit(NDBT_FAILED); usage: ndbout << " (use -h for help)" << endl; wrongargs: return NDBT_ProgramExit(NDBT_WRONGARGS); } // vim: set sw=2 et: