[Assorted-commits] SF.net SVN: assorted:[1285] ydb/trunk/src
Brought to you by:
yangzhang
From: <yan...@us...> - 2009-03-12 19:35:07
|
Revision: 1285 http://assorted.svn.sourceforge.net/assorted/?rev=1285&view=rev Author: yangzhang Date: 2009-03-12 19:34:50 +0000 (Thu, 12 Mar 2009) Log Message: ----------- added tpcc Added Paths: ----------- ydb/trunk/src/tpcc/ ydb/trunk/src/tpcc/Makefile ydb/trunk/src/tpcc/assert.h ydb/trunk/src/tpcc/btree.h ydb/trunk/src/tpcc/clock.cc ydb/trunk/src/tpcc/clock.h ydb/trunk/src/tpcc/randomgenerator.cc ydb/trunk/src/tpcc/randomgenerator.h ydb/trunk/src/tpcc/stlutil.h ydb/trunk/src/tpcc/tpcc.cc ydb/trunk/src/tpcc/tpccclient.cc ydb/trunk/src/tpcc/tpccclient.h ydb/trunk/src/tpcc/tpccdb.cc ydb/trunk/src/tpcc/tpccdb.h ydb/trunk/src/tpcc/tpccgenerator.cc ydb/trunk/src/tpcc/tpccgenerator.h ydb/trunk/src/tpcc/tpcctables.cc ydb/trunk/src/tpcc/tpcctables.h Added: ydb/trunk/src/tpcc/Makefile =================================================================== --- ydb/trunk/src/tpcc/Makefile (rev 0) +++ ydb/trunk/src/tpcc/Makefile 2009-03-12 19:34:50 UTC (rev 1285) @@ -0,0 +1,25 @@ +WARNINGS = -Werror -Wall -Wextra -Wconversion -Wpointer-arith -Wcast-qual -Wcast-align -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare -Wno-unused-parameter + +# Debug flags +CXXFLAGS = -g -MD $(WARNINGS) -I.. +# Optimization flags +#CXXFLAGS = -g -O3 -DNDEBUG -MD $(WARNINGS) + +# Link withthe C++ standard library +LDFLAGS=-lstdc++ + +BINARIES = tpcc # btree_test randomgenerator_test tpccclient_test tpcctables_test tpccgenerator_test tpcc + +all: $(BINARIES) + +#btree_test: btree_test.o stupidunit.o +#randomgenerator_test: randomgenerator_test.o randomgenerator.o stupidunit.o +#tpccclient_test: tpccclient_test.o tpccclient.o randomgenerator.o stupidunit.o +#tpcctables_test: tpcctables_test.o tpcctables.o tpccdb.o randomgenerator.o stupidunit.o +#tpccgenerator_test: tpccgenerator_test.o tpccgenerator.o tpcctables.o tpccdb.o randomgenerator.o stupidunit.o +tpcc: tpcc.o tpccclient.o tpccgenerator.o tpcctables.o tpccdb.o clock.o randomgenerator.o + +clean : + rm -f *.o *.d $(BINARIES) + +-include *.d Added: ydb/trunk/src/tpcc/assert.h =================================================================== --- ydb/trunk/src/tpcc/assert.h (rev 0) +++ ydb/trunk/src/tpcc/assert.h 2009-03-12 19:34:50 UTC (rev 1285) @@ -0,0 +1,16 @@ +#ifndef ASSERT_H__ +#define ASSERT_H__ + +#include <cassert> + +// Wraps the standard assert macro to avoids "unused variable" warnings when compiled away. +// Inspired by: http://powerof2games.com/node/10 +// This is not the "default" because it does not conform to the requirements of the C standard, +// which requires that the NDEBUG version be ((void) 0). +#ifdef NDEBUG +#define ASSERT(x) do { (void)sizeof(x); } while(0) +#else +#define ASSERT(x) assert(x) +#endif + +#endif Added: ydb/trunk/src/tpcc/btree.h =================================================================== --- ydb/trunk/src/tpcc/btree.h (rev 0) +++ ydb/trunk/src/tpcc/btree.h 2009-03-12 19:34:50 UTC (rev 1285) @@ -0,0 +1,511 @@ +#if !defined BPLUSTREE_HPP_227824 +#define BPLUSTREE_HPP_227824 + +// This is required for glibc to define std::posix_memalign +#if !defined(_XOPEN_SOURCE) || (_XOPEN_SOURCE < 600) +#define _XOPEN_SOURCE 600 +#endif + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <boost/static_assert.hpp> +#include <boost/pool/object_pool.hpp> + +// DEBUG +#include <iostream> +using std::cout; +using std::endl; + +#ifdef __linux__ +#define HAVE_POSIX_MEMALIGN +#endif + +#ifdef HAVE_POSIX_MEMALIGN +// Nothing to do +#else +// TODO: This is not aligned! It doesn't matter for this implementation, but it could +static inline int posix_memalign(void** result, int, size_t bytes) { + *result = malloc(bytes); + return *result == NULL; +} +#endif + +template <typename KEY, typename VALUE, unsigned N, unsigned M, + unsigned INNER_NODE_PADDING= 0, unsigned LEAF_NODE_PADDING= 0, + unsigned NODE_ALIGNMENT= 64> +class BPlusTree +{ +public: + // N must be greater than two to make the split of + // two inner nodes sensible. + BOOST_STATIC_ASSERT(N>2); + // Leaf nodes must be able to hold at least one element + BOOST_STATIC_ASSERT(M>0); + + // Builds a new empty tree. + BPlusTree() + : depth(0), + root(new_leaf_node()) + { + // DEBUG + // cout << "sizeof(LeafNode)==" << sizeof(LeafNode) << endl; + // cout << "sizeof(InnerNode)==" << sizeof(InnerNode) << endl; + } + + ~BPlusTree() { + // Empty. Memory deallocation is done automatically + // when innerPool and leafPool are destroyed. + } + + // Inserts a pair (key, value). If there is a previous pair with + // the same key, the old value is overwritten with the new one. + void insert(KEY key, VALUE value) { + // GCC warns that this may be used uninitialized, even though that is untrue. + InsertionResult result = { KEY(), 0, 0 }; + bool was_split; + if( depth == 0 ) { + // The root is a leaf node + assert( *reinterpret_cast<NodeType*>(root) == + NODE_LEAF); + was_split= leaf_insert(reinterpret_cast<LeafNode*> + (root), key, value, &result); + } else { + // The root is an inner node + assert( *reinterpret_cast<NodeType*> + (root) == NODE_INNER ); + was_split= inner_insert(reinterpret_cast<InnerNode*> + (root), depth, key, value, &result); + } + if( was_split ) { + // The old root was splitted in two parts. + // We have to create a new root pointing to them + depth++; + root= new_inner_node(); + InnerNode* rootProxy= + reinterpret_cast<InnerNode*>(root); + rootProxy->num_keys= 1; + rootProxy->keys[0]= result.key; + rootProxy->children[0]= result.left; + rootProxy->children[1]= result.right; + } + } + +// Looks for the given key. If it is not found, it returns false, +// if it is found, it returns true and copies the associated value +// unless the pointer is null. +bool find(const KEY& key, VALUE* value= 0) const { + const InnerNode* inner; + register const void* node= root; + register unsigned d= depth, index; + while( d-- != 0 ) { + inner= reinterpret_cast<const InnerNode*>(node); + assert( inner->type == NODE_INNER ); + index= inner_position_for(key, inner->keys, inner->num_keys); + node= inner->children[index]; + } + const LeafNode* leaf= reinterpret_cast<const LeafNode*>(node); + assert( leaf->type == NODE_LEAF ); + index= leaf_position_for(key, leaf->keys, leaf->num_keys); + if( leaf->keys[index] == key ) { + if( value != 0 ) { + *value= leaf->values[index]; + } + if (leaf->values[index]) + return true; + else return false; + } else { + return false; + } +} + + +// Looks for the given key. If it is not found, it returns false, +// if it is found, it returns true and sets +// the associated value to NULL +// Note: del currently leaks memory. Fix later. +bool del(const KEY& key) { + InnerNode* inner; + register void* node= root; + register unsigned d= depth, index; + while( d-- != 0 ) { + inner= reinterpret_cast<InnerNode*>(node); + assert( inner->type == NODE_INNER ); + index= inner_position_for(key, inner->keys, inner->num_keys); + node= inner->children[index]; + } + LeafNode* leaf= reinterpret_cast<LeafNode*>(node); + assert( leaf->type == NODE_LEAF ); + index= leaf_position_for(key, leaf->keys, leaf->num_keys); + if( leaf->keys[index] == key ) { + leaf->values[index] = 0; + return true; + } else { + return false; + } +} + +// Finds the LAST item that is < key. That is, the next item in the tree is not < key, but this +// item is. If we were to insert key into the tree, it would go after this item. This is weird, +// but is easier than implementing iterators. In STL terms, this would be "lower_bound(key)--" +// WARNING: This does *not* work when values are deleted. Thankfully, TPC-C does not use deletes. +bool findLastLessThan(const KEY& key, VALUE* value = 0, KEY* out_key = 0) const { + const void* node = root; + unsigned int d = depth; + while( d-- != 0 ) { + const InnerNode* inner = reinterpret_cast<const InnerNode*>(node); + assert( inner->type == NODE_INNER ); + unsigned int pos = inner_position_for(key, inner->keys, inner->num_keys); + // We need to rewind in the case where they are equal + if (pos > 0 && key == inner->keys[pos-1]) { + pos -= 1; + } + assert(pos == 0 || inner->keys[pos-1] < key); + node = inner->children[pos]; + } + const LeafNode* leaf= reinterpret_cast<const LeafNode*>(node); + assert( leaf->type == NODE_LEAF ); + unsigned int pos = leaf_position_for(key, leaf->keys, leaf->num_keys); + if (pos <= leaf->num_keys) { + pos -= 1; + if (pos < leaf->num_keys && key == leaf->keys[pos]) { + pos -= 1; + } + + if (pos < leaf->num_keys) { + assert(leaf->keys[pos] < key); + if (leaf->values[pos]) { + if (value != NULL) { + *value = leaf->values[pos]; + } + if (out_key != NULL) { + *out_key = leaf->keys[pos]; + } + return true; + } + } + } + + return false; +} + + // Returns the size of an inner node + // It is useful when optimizing performance with cache alignment. + unsigned sizeof_inner_node() const { + return sizeof(InnerNode); + } + + // Returns the size of a leaf node. + // It is useful when optimizing performance with cache alignment. + unsigned sizeof_leaf_node() const { + return sizeof(LeafNode); + } + + +private: + // Used when debugging + enum NodeType {NODE_INNER=0xDEADBEEF, NODE_LEAF=0xC0FFEE}; + + // Leaf nodes store pairs of keys and values. + struct LeafNode { +#ifndef NDEBUG + LeafNode() : type(NODE_LEAF), num_keys(0) {memset(keys,0,sizeof(KEY)*M);} + const NodeType type; +#else + LeafNode() : num_keys(0) {memset(keys,0,sizeof(KEY)*M);} +#endif + unsigned num_keys; + KEY keys[M]; + VALUE values[M]; + // unsigned char _pad[LEAF_NODE_PADDING]; + }; + + // Inner nodes store pointers to other nodes interleaved with keys. + struct InnerNode { +#ifndef NDEBUG + InnerNode() : type(NODE_INNER), num_keys(0) {memset(keys,0,sizeof(KEY)*M);} + const NodeType type; +#else + InnerNode() : num_keys(0) {memset(keys,0,sizeof(KEY)*M);} +#endif + unsigned num_keys; + KEY keys[N]; + void* children[N+1]; + // unsigned char _pad[INNER_NODE_PADDING]; + }; + + // Custom allocator that returns aligned blocks of memory + template <unsigned ALIGNMENT> + struct AlignedMemoryAllocator { + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + static char* malloc(const size_type bytes) + { + void* result; + if( posix_memalign(&result, ALIGNMENT, bytes) != 0 ) { + result= 0; + } + // Alternative: result= std::malloc(bytes); + return reinterpret_cast<char*>(result); + } + static void free(char* const block) + { std::free(block); } + }; + + // Returns a pointer to a fresh leaf node. + LeafNode* new_leaf_node() { + LeafNode* result; + //result= new LeafNode(); + result= leafPool.construct(); + //cout << "New LeafNode at " << result << endl; + return result; + } + + // Frees a leaf node previously allocated with new_leaf_node() + void delete_leaf_node(LeafNode* node) { + assert( node->type == NODE_LEAF ); + //cout << "Deleting LeafNode at " << node << endl; + // Alternatively: delete node; + leafPool.destroy(node); + } + + // Returns a pointer to a fresh inner node. + InnerNode* new_inner_node() { + InnerNode* result; + // Alternatively: result= new InnerNode(); + result= innerPool.construct(); + //cout << "New InnerNode at " << result << endl; + return result; + } + + // Frees an inner node previously allocated with new_inner_node() + void delete_inner_node(InnerNode* node) { + assert( node->type == NODE_INNER ); + //cout << "Deleting InnerNode at " << node << endl; + // Alternatively: delete node; + innerPool.destroy(node); + } + + // Data type returned by the private insertion methods. + struct InsertionResult { + KEY key; + void* left; + void* right; + }; + + // Returns the position where 'key' should be inserted in a leaf node + // that has the given keys. + static unsigned leaf_position_for(const KEY& key, const KEY* keys, + unsigned num_keys) { + // Simple linear search. Faster for small values of N or M + unsigned k= 0; + while((k < num_keys) && (keys[k]<key)) { + ++k; + } + return k; + /* + // Binary search. It is faster when N or M is > 100, + // but the cost of the division renders it useless + // for smaller values of N or M. + XXX--- needs to be re-checked because the linear search + has changed since the last update to the following ---XXX + int left= -1, right= num_keys, middle; + while( right -left > 1 ) { + middle= (left+right)/2; + if( keys[middle] < key) { + left= middle; + } else { + right= middle; + } + } + //assert( right == k ); + return unsigned(right); + */ + } + + // Returns the position where 'key' should be inserted in an inner node + // that has the given keys. + static inline unsigned inner_position_for(const KEY& key, const KEY* keys, + unsigned num_keys) { + // Simple linear search. Faster for small values of N or M + unsigned k= 0; + while((k < num_keys) && ((keys[k]<key) || (keys[k]==key))) { + ++k; + } + return k; + // Binary search is faster when N or M is > 100, + // but the cost of the division renders it useless + // for smaller values of N or M. + } + + bool leaf_insert(LeafNode* node, KEY& key, + VALUE& value, InsertionResult* result) { + assert( node->type == NODE_LEAF ); + assert( node->num_keys <= M ); + bool was_split= false; + // Simple linear search + unsigned i= leaf_position_for(key, node->keys, node->num_keys); + if( node->num_keys == M ) { + // The node was full. We must split it + unsigned treshold= (M+1)/2; + LeafNode* new_sibling= new_leaf_node(); + new_sibling->num_keys= node->num_keys -treshold; + for(unsigned j=0; j < new_sibling->num_keys; ++j) { + new_sibling->keys[j]= node->keys[treshold+j]; + new_sibling->values[j]= + node->values[treshold+j]; + } + node->num_keys= treshold; + if( i < treshold ) { + // Inserted element goes to left sibling + leaf_insert_nonfull(node, key, value, i); + } else { + // Inserted element goes to right sibling + leaf_insert_nonfull(new_sibling, key, value, + i-treshold); + } + // Notify the parent about the split + was_split= true; + result->key= new_sibling->keys[0]; + result->left= node; + result->right= new_sibling; + } else { + // The node was not full + leaf_insert_nonfull(node, key, value, i); + } + return was_split; + } + + static void leaf_insert_nonfull(LeafNode* node, KEY& key, VALUE& value, + unsigned index) { + assert( node->type == NODE_LEAF ); + assert( node->num_keys < M ); + assert( index <= M ); + assert( index <= node->num_keys ); + if( (index < M) && + (node->keys[index] == key) ) { + // We are inserting a duplicate value. + // Simply overwrite the old one + node->values[index]= value; + } else { + // The key we are inserting is unique + for(unsigned i=node->num_keys; i > index; --i) { + node->keys[i]= node->keys[i-1]; + node->values[i]= node->values[i-1]; + } + node->num_keys++; + node->keys[index]= key; + node->values[index]= value; + } + } + + bool inner_insert(InnerNode* node, unsigned current_depth, KEY& key, + VALUE& value, InsertionResult* result) { + assert( node->type == NODE_INNER ); + assert( current_depth != 0 ); + // Early split if node is full. + // This is not the canonical algorithm for B+ trees, + // but it is simpler and does not break the definition. + bool was_split= false; + if( node->num_keys == N ) { + // Split + unsigned treshold= (N+1)/2; + InnerNode* new_sibling= new_inner_node(); + new_sibling->num_keys= node->num_keys -treshold; + for(unsigned i=0; i < new_sibling->num_keys; ++i) { + new_sibling->keys[i]= node->keys[treshold+i]; + new_sibling->children[i]= + node->children[treshold+i]; + } + new_sibling->children[new_sibling->num_keys]= + node->children[node->num_keys]; + node->num_keys= treshold-1; + // Set up the return variable + was_split= true; + result->key= node->keys[treshold-1]; + result->left= node; + result->right= new_sibling; + // Now insert in the appropriate sibling + if( key < result->key ) { + inner_insert_nonfull(node, current_depth, key, value); + } else { + inner_insert_nonfull(new_sibling, current_depth, key, + value); + } + } else { + // No split + inner_insert_nonfull(node, current_depth, key, value); + } + return was_split; + } + + void inner_insert_nonfull(InnerNode* node, unsigned current_depth, KEY& key, + VALUE& value) { + assert( node->type == NODE_INNER ); + assert( node->num_keys < N ); + assert( current_depth != 0 ); + // Simple linear search + unsigned index= inner_position_for(key, node->keys, + node->num_keys); + // GCC warns that this may be used uninitialized, even though that is untrue. + InsertionResult result = { KEY(), 0, 0 }; + bool was_split; + if( current_depth-1 == 0 ) { + // The children are leaf nodes + for(unsigned kk=0; kk < node->num_keys+1; ++kk) { + assert( *reinterpret_cast<NodeType*> + (node->children[kk]) == NODE_LEAF ); + } + was_split= leaf_insert(reinterpret_cast<LeafNode*> + (node->children[index]), key, value, &result); + } else { + // The children are inner nodes + for(unsigned kk=0; kk < node->num_keys+1; ++kk) { + assert( *reinterpret_cast<NodeType*> + (node->children[kk]) == NODE_INNER ); + } + InnerNode* child= reinterpret_cast<InnerNode*> + (node->children[index]); + was_split= inner_insert( child, current_depth-1, key, value, + &result); + } + if( was_split ) { + if( index == node->num_keys ) { + // Insertion at the rightmost key + node->keys[index]= result.key; + node->children[index]= result.left; + node->children[index+1]= result.right; + node->num_keys++; + } else { + // Insertion not at the rightmost key + node->children[node->num_keys+1]= + node->children[node->num_keys]; + for(unsigned i=node->num_keys; i!=index; --i) { + node->children[i]= node->children[i-1]; + node->keys[i]= node->keys[i-1]; + } + node->children[index]= result.left; + node->children[index+1]= result.right; + node->keys[index]= result.key; + node->num_keys++; + } + } // else the current node is not affected + } + + typedef AlignedMemoryAllocator<NODE_ALIGNMENT> AlignedAllocator; + + // Node memory allocators. IMPORTANT NOTE: they must be declared + // before the root to make sure that they are properly initialised + // before being used to allocate any node. + boost::object_pool<InnerNode, AlignedAllocator> innerPool; + boost::object_pool<LeafNode, AlignedAllocator> leafPool; + // Depth of the tree. A tree of depth 0 only has a leaf node. + unsigned depth; + // Pointer to the root node. It may be a leaf or an inner node, but + // it is never null. + void* root; +}; + +#endif // !defined BPLUSTREE_HPP_227824 Added: ydb/trunk/src/tpcc/clock.cc =================================================================== --- ydb/trunk/src/tpcc/clock.cc (rev 0) +++ ydb/trunk/src/tpcc/clock.cc 2009-03-12 19:34:50 UTC (rev 1285) @@ -0,0 +1,58 @@ +#include "clock.h" + +#include <sys/time.h> + +#include <cstdio> +#include <ctime> + +#include "assert.h" + +// Fills output with the base-10 ASCII representation of value, using digits digits. +static char* makeInt(char* output, int value, int digits) { + char* last = output + digits; + char* next = last; + for (int i = 0; i < digits; ++i) { + int digit = value % 10; + value = value / 10; + next -= 1; + *next = static_cast<char>('0' + digit); + } + assert(value == 0); + return last; +} + +void SystemClock::getDateTimestamp(char* now) { + // Get the system time. Convert it to local time + time_t seconds_since_epoch = time(NULL); + assert(seconds_since_epoch != -1); + + struct tm local_calendar; + struct tm* result = localtime_r(&seconds_since_epoch, &local_calendar); + ASSERT(result == &local_calendar); + + // Format the time + // strftime is slow: it ends up consulting timezone info + // snprintf is also slow, since it needs to parse the input string. This is significantly + // faster, saving ~10% of the run time. + //~ int bytes = snprintf(now, DATETIME_SIZE+1, "%04d%02d%02d%02d%02d%02d", + //~ local_calendar.tm_year+1900, local_calendar.tm_mon+1, local_calendar.tm_mday, + //~ local_calendar.tm_hour, local_calendar.tm_min, local_calendar.tm_sec); + //~ int bytes = strftime(now, DATETIME_SIZE+1, "%Y%m%d%H%M%S", &broken_down_local_time); + char* next = makeInt(now, local_calendar.tm_year+1900, 4); + next = makeInt(next, local_calendar.tm_mon+1, 2); + next = makeInt(next, local_calendar.tm_mday, 2); + next = makeInt(next, local_calendar.tm_hour, 2); + next = makeInt(next, local_calendar.tm_min, 2); + next = makeInt(next, local_calendar.tm_sec, 2); + *next = '\0'; + assert(next == now + DATETIME_SIZE); +} + +int64_t SystemClock::getMicroseconds() { + struct timeval time; + int error = gettimeofday(&time, NULL); + ASSERT(error == 0); + int64_t result = time.tv_sec * 1000000; + result += time.tv_usec; + return result; +} Added: ydb/trunk/src/tpcc/clock.h =================================================================== --- ydb/trunk/src/tpcc/clock.h (rev 0) +++ ydb/trunk/src/tpcc/clock.h 2009-03-12 19:34:50 UTC (rev 1285) @@ -0,0 +1,28 @@ +#ifndef CLOCK_H__ +#define CLOCK_H__ + +//~ #include <cstdint> +#include <stdint.h> + +// Interface to the real time system clock. +class Clock { +public: + virtual ~Clock() {} + + static const int DATETIME_SIZE = 14; + + // now must have at least DATETIME_SIZE+1 bytes. + virtual void getDateTimestamp(char* now) = 0; + + // Returns the number of microseconds since the epoch. + virtual int64_t getMicroseconds() = 0; +}; + +// Uses gettimeofday. +class SystemClock : public Clock { +public: + virtual void getDateTimestamp(char* now); + virtual int64_t getMicroseconds(); +}; + +#endif Added: ydb/trunk/src/tpcc/randomgenerator.cc =================================================================== --- ydb/trunk/src/tpcc/randomgenerator.cc (rev 0) +++ ydb/trunk/src/tpcc/randomgenerator.cc 2009-03-12 19:34:50 UTC (rev 1285) @@ -0,0 +1,178 @@ +#include "randomgenerator.h" + +#include <algorithm> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> + +#include "assert.h" + +NURandC NURandC::makeRandom(RandomGenerator* generator) { + NURandC c; + c.c_last_ = generator->number(0, 255); + c.c_id_ = generator->number(0, 1023); + c.ol_i_id_ = generator->number(0, 8191); + return c; +} + +// Returns true if the C-Run value is valid. See TPC-C 2.1.6.1 (page 20). +static bool validCRun(int cRun, int cLoad) { + int cDelta = abs(cRun - cLoad); + return 65 <= cDelta && cDelta <= 119 && cDelta != 96 && cDelta != 112; +} + +NURandC NURandC::makeRandomForRun(RandomGenerator* generator, const NURandC& c_load) { + NURandC c = makeRandom(generator); + + while (!validCRun(c.c_last_, c_load.c_last_)) { + c.c_last_ = generator->number(0, 255); + } + ASSERT(validCRun(c.c_last_, c_load.c_last_)); + + return c; +} + +int RandomGenerator::numberExcluding(int lower, int upper, int excluding) { + ASSERT(lower < upper); + ASSERT(lower <= excluding && excluding <= upper); + + // Generate 1 less number than the range + int num = number(lower, upper-1); + + // Adjust the numbers to remove excluding + if (num >= excluding) { + num += 1; + } + ASSERT(lower <= num && num <= upper && num != excluding); + return num; +} + +static void generateString(RandomGenerator* generator, char* s, int lower_length, int upper_length, + char base_character, int num_characters) { + int length = generator->number(lower_length, upper_length); + for (int i = 0; i < length; ++i) { + s[i] = static_cast<char>(base_character + generator->number(0, num_characters-1)); + } + s[length] = '\0'; +} + +void RandomGenerator::astring(char* s, int lower_length, int upper_length) { + generateString(this, s, lower_length, upper_length, 'a', 26); +} + +void RandomGenerator::nstring(char* s, int lower_length, int upper_length) { + generateString(this, s, lower_length, upper_length, '0', 10); +} + +void RandomGenerator::lastName(char* c_last, int max_cid) { + makeLastName(NURand(255, 0, std::min(999, max_cid-1)), c_last); +} + +float RandomGenerator::fixedPoint(int digits, float lower, float upper) { + int multiplier = 1; + for (int i = 0; i < digits; ++i) { + multiplier *= 10; + } + + int int_lower = static_cast<int>(lower * static_cast<double>(multiplier) + 0.5); + int int_upper = static_cast<int>(upper * static_cast<double>(multiplier) + 0.5); + return (float) number(int_lower, int_upper) / (float) multiplier; +} + +int RandomGenerator::NURand(int A, int x, int y) { + int C = 0; + switch(A) { + case 255: + C = c_values_.c_last_; + break; + case 1023: + C = c_values_.c_id_; + break; + case 8191: + C = c_values_.ol_i_id_; + break; + default: + fprintf(stderr, "Error: NURand: A = %d not supported\n", A); + exit(1); + } + return (((number(0, A) | number(x, y)) + C) % (y - x + 1)) + x; +} + +int* RandomGenerator::makePermutation(int lower, int upper) { + // initialize with consecutive values + int* array = new int[upper - lower + 1]; + for (int i = 0; i <= upper - lower; ++i) { + array[i] = lower + i; + } + + for (int i = 0; i < upper - lower; ++i) { + // choose a value to go into this position, including this position + int index = number(i, upper - lower); + int temp = array[i]; + array[i] = array[index]; + array[index] = temp; + } + + return array; +} + +// Defined by TPC-C 4.3.2.3. +void makeLastName(int num, char* name) { + static const char* const SYLLABLES[] = { + "BAR", "OUGHT", "ABLE", "PRI", "PRES", "ESE", "ANTI", "CALLY", "ATION", "EING", }; + static const int LENGTHS[] = { 3, 5, 4, 3, 4, 3, 4, 5, 5, 4, }; + + ASSERT(0 <= num && num <= 999); + int indicies[] = { num/100, (num/10)%10, num%10 }; + + int offset = 0; + for (int i = 0; i < sizeof(indicies)/sizeof(*indicies); ++i) { + ASSERT(strlen(SYLLABLES[indicies[i]]) == LENGTHS[indicies[i]]); + memcpy(name + offset, SYLLABLES[indicies[i]], LENGTHS[indicies[i]]); + offset += LENGTHS[indicies[i]]; + } + name[offset] = '\0'; +} + +RealRandomGenerator::RealRandomGenerator() { +#ifdef HAVE_RANDOM_R + // Set the random state to zeros. glibc will attempt to access the old state if not NULL. + memset(&state, 0, sizeof(state)); + int result = initstate_r(static_cast<unsigned int>(time(NULL)), state_array, + sizeof(state_array), &state); + ASSERT(result == 0); +#else + seed(time(NULL)); +#endif +} + +int RealRandomGenerator::number(int lower, int upper) { + int rand_int; +#ifdef HAVE_RANDOM_R + int error = random_r(&state, &rand_int); + ASSERT(error == 0); +#else + rand_int = nrand48(state); +#endif + ASSERT(0 <= rand_int && rand_int <= RAND_MAX); + + // Select a number in [0, range_size-1] + int range_size = upper - lower + 1; + rand_int %= range_size; + ASSERT(0 <= rand_int && rand_int < range_size); + + // Shift the range to [lower, upper] + rand_int += lower; + ASSERT(lower <= rand_int && rand_int <= upper); + return rand_int; +} + +void RealRandomGenerator::seed(unsigned int seed) { +#ifdef HAVE_RANDOM_R + int error = srandom_r(seed, &state); + ASSERT(error == 0); +#else + memcpy(state, &seed, std::min(sizeof(seed), sizeof(state))); +#endif +} Added: ydb/trunk/src/tpcc/randomgenerator.h =================================================================== --- ydb/trunk/src/tpcc/randomgenerator.h (rev 0) +++ ydb/trunk/src/tpcc/randomgenerator.h 2009-03-12 19:34:50 UTC (rev 1285) @@ -0,0 +1,102 @@ +#ifndef RANDOMGENERATOR_H__ +#define RANDOMGENERATOR_H__ + +#include <cstdlib> // for struct random_data + +#ifdef __linux__ +#define HAVE_RANDOM_R +#endif + +class RandomGenerator; + +// Constant C values for the NURand function. +struct NURandC { + NURandC() : c_last_(0), c_id_(0), ol_i_id_(0) {} + + int c_last_; + int c_id_; + int ol_i_id_; + + // Sets the fields randomly. + static NURandC makeRandom(RandomGenerator* generator); + + // Sets the fields randomly, in a fashion acceptable for a test run. c_load is the value of + // c_last_ that was used to generate the tables. See TPC-C 2.1.6.1. (page 20). + static NURandC makeRandomForRun(RandomGenerator* generator, const NURandC& c_load); +}; + +class RandomGenerator { +public: + RandomGenerator() : c_values_(NURandC()) {} + virtual ~RandomGenerator() {} + + // Return a random integer in the range [lower, upper]. The range is inclusive. + virtual int number(int lower, int upper) = 0; + + // Return a random integer in the range [lower, upper] excluding excluded. The range is + // inclusive. + int numberExcluding(int lower, int upper, int excluding); + + void astring(char* s, int lower_length, int upper_length); + void nstring(char* s, int lower_length, int upper_length); + + // Fill name with a random last name, generated according to TPC-C rules. Limits the customer + // id for the generated name to cid. + void lastName(char* name, int max_cid); + + float fixedPoint(int digits, float lower, float upper); + + // Non-uniform random number function from TPC-C 2.1.6. (page 20). + int NURand(int A, int x, int y); + + int* makePermutation(int lower, int upper); + + void setC(const NURandC& c) { + c_values_ = c; + } + +private: + NURandC c_values_; +}; + +// A mock RandomGenerator for unit testing. +class MockRandomGenerator : public RandomGenerator { +public: + MockRandomGenerator() : minimum_(true) {} + + virtual int number(int lower, int upper) { + if (minimum_) return lower; + else return upper; + } + + bool minimum_; +}; + +static const int MAX_LAST_NAME = 16; + +// Generate a last name as defined by TPC-C 4.3.2.3. name must be at least MAX_LAST_NAME+1 bytes. +void makeLastName(int num, char* name); + +// A real RandomGenerator that uses random_r. +class RealRandomGenerator : public RandomGenerator { +public: + // Seeds the generator with the current time. + RealRandomGenerator(); + + virtual int number(int lower, int upper); + + // Seed the generator with seed. + void seed(unsigned int seed); + +private: +#ifdef HAVE_RANDOM_R + // man random says optimal sizes are 8, 32, 64, 128, 256 bytes + static const int RANDOM_STATE_SIZE = 64; + char state_array[RANDOM_STATE_SIZE]; + struct random_data state; +#else + unsigned short state[3]; +#endif +}; + +#endif Added: ydb/trunk/src/tpcc/stlutil.h =================================================================== --- ydb/trunk/src/tpcc/stlutil.h (rev 0) +++ ydb/trunk/src/tpcc/stlutil.h 2009-03-12 19:34:50 UTC (rev 1285) @@ -0,0 +1,24 @@ +#ifndef STLUTIL_H__ +#define STLUTIL_H__ + +// Deletes all elements in STL container. +template <typename T> +static void STLDeleteElements(T* container) { + const typename T::iterator end = container->end(); + for (typename T::iterator i = container->begin(); i != end; ++i) { + delete *i; + } + container->clear(); +}; + +// Deletes all values (iterator->second) in STL container. +template <typename T> +static void STLDeleteValues(T* container) { + const typename T::iterator end = container->end(); + for (typename T::iterator i = container->begin(); i != end; ++i) { + delete i->second; + } + container->clear(); +}; + +#endif Added: ydb/trunk/src/tpcc/tpcc.cc =================================================================== --- ydb/trunk/src/tpcc/tpcc.cc (rev 0) +++ ydb/trunk/src/tpcc/tpcc.cc 2009-03-12 19:34:50 UTC (rev 1285) @@ -0,0 +1,72 @@ +#define __STDC_FORMAT_MACROS +#include <climits> +#include <inttypes.h> + +#include "clock.h" +#include "randomgenerator.h" +#include "tpccclient.h" +#include "tpccgenerator.h" +#include "tpcctables.h" + + +int main(int argc, const char* argv[]) { + if (argc != 2) { + fprintf(stderr, "tpcc [num warehouses]\n"); + exit(1); + } + + long num_warehouses = strtol(argv[1], NULL, 10); + if (num_warehouses == LONG_MIN || num_warehouses == LONG_MAX) { + fprintf(stderr, "Bad warehouse number (%s)\n", argv[1]); + exit(1); + } + if (num_warehouses <= 0) { + fprintf(stderr, "Number of warehouses must be > 0 (was %ld)\n", num_warehouses); + exit(1); + } + if (num_warehouses > Warehouse::MAX_WAREHOUSE_ID) { + fprintf(stderr, "Number of warehouses must be <= %d (was %ld)\n", Warehouse::MAX_WAREHOUSE_ID, num_warehouses); + exit(1); + } + + TPCCTables* tables = new TPCCTables(); + SystemClock* clock = new SystemClock(); + + // Create a generator for filling the database. + RealRandomGenerator* random = new RealRandomGenerator(); + NURandC cLoad = NURandC::makeRandom(random); + random->setC(cLoad); + + // Generate the data + printf("Loading %ld warehouses... ", num_warehouses); + fflush(stdout); + char now[Clock::DATETIME_SIZE+1]; + clock->getDateTimestamp(now); + TPCCGenerator generator(random, now, Item::NUM_ITEMS, District::NUM_PER_WAREHOUSE, + Customer::NUM_PER_DISTRICT, NewOrder::INITIAL_NUM_PER_DISTRICT); + int64_t begin = clock->getMicroseconds(); + generator.makeItemsTable(tables); + for (int i = 0; i < num_warehouses; ++i) { + generator.makeWarehouse(tables, i+1); + } + int64_t end = clock->getMicroseconds(); + printf("%"PRId64" ms\n", (end-begin)/1000); + + // Change the constants for run + random = new RealRandomGenerator(); + random->setC(NURandC::makeRandomForRun(random, cLoad)); + + // Client owns all the parameters + TPCCClient client(clock, random, tables, Item::NUM_ITEMS, static_cast<int>(num_warehouses), + District::NUM_PER_WAREHOUSE, Customer::NUM_PER_DISTRICT); + printf("Running... "); + fflush(stdout); + begin = clock->getMicroseconds(); + for (int i = 0; i < 200000; ++i) { + client.doOne(); + } + end = clock->getMicroseconds(); + printf("%"PRId64" ms\n", (end-begin)/1000); + + return 0; +} Added: ydb/trunk/src/tpcc/tpccclient.cc =================================================================== --- ydb/trunk/src/tpcc/tpccclient.cc (rev 0) +++ ydb/trunk/src/tpcc/tpccclient.cc 2009-03-12 19:34:50 UTC (rev 1285) @@ -0,0 +1,175 @@ +#include "tpccclient.h" + +#include <cstdio> +#include <vector> + +#include "assert.h" +#include "clock.h" +#include "randomgenerator.h" +#include "tpccdb.h" + +using std::vector; + +// Non-integral constants must be defined in a .cc file. Needed for Mac OS X. +// http://www.research.att.com/~bs/bs_faq2.html#in-class +const float TPCCClient::MIN_PAYMENT_AMOUNT; +const float TPCCClient::MAX_PAYMENT_AMOUNT; + +TPCCClient::TPCCClient(Clock* clock, RandomGenerator* generator, TPCCDB* db, int num_items, + int num_warehouses, int districts_per_warehouse, int customers_per_district) : + clock_(clock), + generator_(generator), + db_(db), + num_items_(num_items), + num_warehouses_(num_warehouses), + districts_per_warehouse_(districts_per_warehouse), + customers_per_district_(customers_per_district) { + ASSERT(clock_ != NULL); + ASSERT(generator_ != NULL); + ASSERT(db_ != NULL); + ASSERT(1 <= num_items_ && num_items_ <= Item::NUM_ITEMS); + ASSERT(1 <= num_warehouses_ && num_warehouses_ <= Warehouse::MAX_WAREHOUSE_ID); + ASSERT(1 <= districts_per_warehouse_ && + districts_per_warehouse_ <= District::NUM_PER_WAREHOUSE); + ASSERT(1 <= customers_per_district_ && customers_per_district_ <= Customer::NUM_PER_DISTRICT); +} + +TPCCClient::~TPCCClient() { + delete clock_; + delete generator_; + delete db_; +} + +void TPCCClient::doStockLevel() { + int32_t threshold = generator_->number(MIN_STOCK_LEVEL_THRESHOLD, MAX_STOCK_LEVEL_THRESHOLD); + int result = db_->stockLevel(generateWarehouse(), generateDistrict(), threshold); + ASSERT(result >= 0); +} + +void TPCCClient::doOrderStatus() { + OrderStatusOutput output; + int y = generator_->number(1, 100); + if (y <= 60) { + // 60%: order status by last name + char c_last[Customer::MAX_LAST+1]; + generator_->lastName(c_last, customers_per_district_); + db_->orderStatus(generateWarehouse(), generateDistrict(), c_last, &output); + } else { + // 40%: order status by id + ASSERT(y > 60); + db_->orderStatus(generateWarehouse(), generateDistrict(), generateCID(), &output); + } +} + +void TPCCClient::doDelivery() { + int carrier = generator_->number(Order::MIN_CARRIER_ID, Order::MAX_CARRIER_ID); + char now[Clock::DATETIME_SIZE+1]; + clock_->getDateTimestamp(now); + + vector<DeliveryOrderInfo> orders; + db_->delivery(generateWarehouse(), carrier, now, &orders); + if (orders.size() != District::NUM_PER_WAREHOUSE) { + printf("Only delivered from %zd districts\n", orders.size()); + } +} + +void TPCCClient::doPayment() { + PaymentOutput output; + int x = generator_->number(1, 100); + int y = generator_->number(1, 100); + + int32_t w_id = generateWarehouse(); + int32_t d_id = generateDistrict(); + + int32_t c_w_id; + int32_t c_d_id; + if (num_warehouses_ == 1 || x <= 85) { + // 85%: paying through own warehouse (or there is only 1 warehouse) + c_w_id = w_id; + c_d_id = d_id; + } else { + // 15%: paying through another warehouse: + // select in range [1, num_warehouses] excluding w_id + c_w_id = generator_->numberExcluding(1, num_warehouses_, w_id); + ASSERT(c_w_id != w_id); + c_d_id = generateDistrict(); + } + float h_amount = generator_->fixedPoint(2, MIN_PAYMENT_AMOUNT, MAX_PAYMENT_AMOUNT); + + char now[Clock::DATETIME_SIZE+1]; + clock_->getDateTimestamp(now); + if (y <= 60) { + // 60%: payment by last name + char c_last[Customer::MAX_LAST+1]; + generator_->lastName(c_last, customers_per_district_); + db_->payment(w_id, d_id, c_w_id, c_d_id, c_last, h_amount, now, &output); + } else { + // 40%: payment by id + ASSERT(y > 60); + db_->payment(w_id, d_id, c_w_id, c_d_id, generateCID(), h_amount, now, &output); + } +} + +void TPCCClient::doNewOrder() { + int32_t w_id = generateWarehouse(); + int ol_cnt = generator_->number(Order::MIN_OL_CNT, Order::MAX_OL_CNT); + + // 1% of transactions roll back + bool rollback = generator_->number(1, 100) == 1; + + vector<NewOrderItem> items(ol_cnt); + for (int i = 0; i < ol_cnt; ++i) { + if (rollback && i+1 == ol_cnt) { + items[i].i_id = Item::NUM_ITEMS + 1; + } else { + items[i].i_id = generateItemID(); + } + + bool remote = generator_->number(1, 100) == 1; + if (num_warehouses_ > 1 && remote) { + items[i].ol_supply_w_id = generator_->numberExcluding(1, num_warehouses_, w_id); + } else { + items[i].ol_supply_w_id = w_id; + } + items[i].ol_quantity = generator_->number(1, MAX_OL_QUANTITY); + } + + char now[Clock::DATETIME_SIZE+1]; + clock_->getDateTimestamp(now); + NewOrderOutput output; + db_->newOrder(w_id, generateDistrict(), generateCID(), items, now, &output); +} + +void TPCCClient::doOne() { + // This is not strictly accurate: The requirement is for certain *minimum* percentages to be + // maintained. This is close to the right thing, but not precisely correct. + // See TPC-C 5.2.4 (page 68). + int x = generator_->number(1, 100); + if (x <= 4) { // 4% + doStockLevel(); + } else if (x <= 8) { // 4% + doDelivery(); + } else if (x <= 12) { // 4% + doOrderStatus(); + } else if (x <= 12+43) { // 43% + doPayment(); + } else { // 45% + ASSERT(x > 100-45); + doNewOrder(); + } +} + +int32_t TPCCClient::generateWarehouse() { + return generator_->number(1, num_warehouses_); +} + +int32_t TPCCClient::generateDistrict() { + return generator_->number(1, districts_per_warehouse_); +} +int32_t TPCCClient::generateCID() { + return generator_->NURand(1023, 1, customers_per_district_); +} + +int32_t TPCCClient::generateItemID() { + return generator_->NURand(8191, 1, num_items_); +} Added: ydb/trunk/src/tpcc/tpccclient.h =================================================================== --- ydb/trunk/src/tpcc/tpccclient.h (rev 0) +++ ydb/trunk/src/tpcc/tpccclient.h 2009-03-12 19:34:50 UTC (rev 1285) @@ -0,0 +1,51 @@ +#ifndef TPCCCLIENT_H__ +#define TPCCCLIENT_H__ + +//~ #include <cstdint> +#include <stdint.h> + +class Clock; +class RandomGenerator; +class TPCCDB; + +// Generates transactions according to the TPC-C specification. This ignores the fact that +// terminals have a fixed w_id, d_id, and that requests should be made after a minimum keying time +// and a think time. +class TPCCClient { +public: + // Owns clock, generator and db. + TPCCClient(Clock* clock, RandomGenerator* generator, TPCCDB* db, int num_items, + int num_warehouses, int districts_per_warehouse, int customers_per_district); + ~TPCCClient(); + + void doStockLevel(); + void doOrderStatus(); + void doDelivery(); + void doPayment(); + void doNewOrder(); + + void doOne(); + + static const int32_t MIN_STOCK_LEVEL_THRESHOLD = 10; + static const int32_t MAX_STOCK_LEVEL_THRESHOLD = 20; + // TODO: Should these constants be part of tpccdb.h? + static const float MIN_PAYMENT_AMOUNT = 1.00; + static const float MAX_PAYMENT_AMOUNT = 5000.00; + static const int32_t MAX_OL_QUANTITY = 10; + +private: + int32_t generateWarehouse(); + int32_t generateDistrict(); + int32_t generateCID(); + int32_t generateItemID(); + + Clock* clock_; + RandomGenerator* generator_; + TPCCDB* db_; + int num_items_; + int num_warehouses_; + int districts_per_warehouse_; + int customers_per_district_; +}; + +#endif Added: ydb/trunk/src/tpcc/tpccdb.cc =================================================================== --- ydb/trunk/src/tpcc/tpccdb.cc (rev 0) +++ ydb/trunk/src/tpcc/tpccdb.cc 2009-03-12 19:34:50 UTC (rev 1285) @@ -0,0 +1,23 @@ +#include "tpccdb.h" + +// Non-integral constants must be defined in a .cc file. Needed for Mac OS X. +// http://www.research.att.com/~bs/bs_faq2.html#in-class +const float Item::MIN_PRICE; +const float Item::MAX_PRICE; +const float Warehouse::MIN_TAX; +const float Warehouse::MAX_TAX; +const float Warehouse::INITIAL_YTD; +const float District::MIN_TAX; +const float District::MAX_TAX; +const float District::INITIAL_YTD; // different from Warehouse +const float Customer::MIN_DISCOUNT; +const float Customer::MAX_DISCOUNT; +const float Customer::INITIAL_BALANCE; +const float Customer::INITIAL_CREDIT_LIM; +const float Customer::INITIAL_YTD_PAYMENT; +const char Customer::GOOD_CREDIT[] = "GC"; +const char Customer::BAD_CREDIT[] = "BC"; +const float OrderLine::MIN_AMOUNT; +const float OrderLine::MAX_AMOUNT; +const char NewOrderOutput::INVALID_ITEM_STATUS[] = "Item number is not valid"; +const float History::INITIAL_AMOUNT; Added: ydb/trunk/src/tpcc/tpccdb.h =================================================================== --- ydb/trunk/src/tpcc/tpccdb.h (rev 0) +++ ydb/trunk/src/tpcc/tpccdb.h 2009-03-12 19:34:50 UTC (rev 1285) @@ -0,0 +1,341 @@ +#ifndef TPCCDB_H__ +#define TPCCDB_H__ + +#include <stdint.h> +#include <vector> + +struct Item { + static const int MIN_IM = 1; + static const int MAX_IM = 10000; + static const float MIN_PRICE = 1.00; + static const float MAX_PRICE = 100.00; + static const int MIN_NAME = 14; + static const int MAX_NAME = 24; + static const int MIN_DATA = 26; + static const int MAX_DATA = 50; + static const int NUM_ITEMS = 100000; + + int32_t i_id; + int32_t i_im_id; + float i_price; + char i_name[MAX_NAME+1]; + char i_data[MAX_DATA+1]; +}; + +struct Warehouse { + static const float MIN_TAX = 0; + static const float MAX_TAX = 0.2000f; + static const float INITIAL_YTD = 300000.00f; + static const int MIN_NAME = 6; + static const int MAX_NAME = 10; + static const int MIN_STREET = 10; + static const int MAX_STREET = 20; + static const int MIN_CITY = 10; + static const int MAX_CITY = 20; + static const int STATE = 2; + static const int ZIP = 9; + // TPC-C 1.3.1 (page 11) requires 2*W. This permits testing up to 50 warehouses. This is an + // arbitrary limit created to pack ids into integers. + static const int MAX_WAREHOUSE_ID = 100; + + int32_t w_id; + float w_tax; + float w_ytd; + char w_name[MAX_NAME+1]; + char w_street_1[MAX_STREET+1]; + char w_street_2[MAX_STREET+1]; + char w_city[MAX_CITY+1]; + char w_state[STATE+1]; + char w_zip[ZIP+1]; +}; + +struct District { + static const float MIN_TAX = 0; + static const float MAX_TAX = 0.2000f; + static const float INITIAL_YTD = 30000.00; // different from Warehouse + static const int INITIAL_NEXT_O_ID = 3001; + static const int MIN_NAME = 6; + static const int MAX_NAME = 10; + static const int MIN_STREET = 10; + static const int MAX_STREET = 20; + static const int MIN_CITY = 10; + static const int MAX_CITY = 20; + static const int STATE = 2; + static const int ZIP = 9; + static const int NUM_PER_WAREHOUSE = 10; + + int32_t d_id; + int32_t d_w_id; + float d_tax; + float d_ytd; + int32_t d_next_o_id; + char d_name[MAX_NAME+1]; + char d_street_1[MAX_STREET+1]; + char d_street_2[MAX_STREET+1]; + char d_city[MAX_CITY+1]; + char d_state[STATE+1]; + char d_zip[ZIP+1]; +}; + +struct Stock { + static const int MIN_QUANTITY = 10; + static const int MAX_QUANTITY = 100; + static const int DIST = 24; + static const int MIN_DATA = 26; + static const int MAX_DATA = 50; + static const int NUM_STOCK_PER_WAREHOUSE = 100000; + + int32_t s_i_id; + int32_t s_w_id; + int32_t s_quantity; + int32_t s_ytd; + int32_t s_order_cnt; + int32_t s_remote_cnt; + char s_dist[District::NUM_PER_WAREHOUSE][DIST+1]; + char s_data[MAX_DATA+1]; +}; + +// YYYY-MM-DD HH:MM:SS This is supposed to be a date/time field from Jan 1st 1900 - +// Dec 31st 2100 with a resolution of 1 second. See TPC-C 1.3.1. +static const int DATETIME_SIZE = 14; + +struct Customer { + static const float INITIAL_CREDIT_LIM = 50000.00; + static const float MIN_DISCOUNT = 0.0000; + static const float MAX_DISCOUNT = 0.5000; + static const float INITIAL_BALANCE = -10.00; + static const float INITIAL_YTD_PAYMENT = 10.00; + static const int INITIAL_PAYMENT_CNT = 1; + static const int INITIAL_DELIVERY_CNT = 0; + static const int MIN_FIRST = 6; + static const int MAX_FIRST = 10; + static const int MIDDLE = 2; + static const int MAX_LAST = 16; + static const int MIN_STREET = 10; + static const int MAX_STREET = 20; + static const int MIN_CITY = 10; + static const int MAX_CITY = 20; + static const int STATE = 2; + static const int ZIP = 9; + static const int PHONE = 16; + static const int CREDIT = 2; + static const int MIN_DATA = 300; + static const int MAX_DATA = 500; + static const int NUM_PER_DISTRICT = 3000; + static const char GOOD_CREDIT[]; + static const char BAD_CREDIT[]; + + int32_t c_id; + int32_t c_d_id; + int32_t c_w_id; + float c_credit_lim; + float c_discount; + float c_balance; + float c_ytd_payment; + int32_t c_payment_cnt; + int32_t c_delivery_cnt; + char c_first[MAX_FIRST+1]; + char c_middle[MIDDLE+1]; + char c_last[MAX_LAST+1]; + char c_street_1[MAX_STREET+1]; + char c_street_2[MAX_STREET+1]; + char c_city[MAX_CITY+1]; + char c_state[STATE+1]; + char c_zip[ZIP+1]; + char c_phone[PHONE+1]; + char c_since[DATETIME_SIZE+1]; + char c_credit[CREDIT+1]; + char c_data[MAX_DATA+1]; +}; + +struct Order { + static const int MIN_CARRIER_ID = 1; + static const int MAX_CARRIER_ID = 10; + // HACK: This is not strictly correct, but it works + static const int NULL_CARRIER_ID = 0; + // Less than this value, carrier != null, >= -> carrier == null + static const int NULL_CARRIER_LOWER_BOUND = 2101; + static const int MIN_OL_CNT = 5; + static const int MAX_OL_CNT = 15; + static const int INITIAL_ALL_LOCAL = 1; + static const int INITIAL_ORDERS_PER_DISTRICT = 3000; + // See TPC-C 1.3.1 (page 15) + static const int MAX_ORDER_ID = 10000000; + + int32_t o_id; + int32_t o_c_id; + int32_t o_d_id; + int32_t o_w_id; + int32_t o_carrier_id; + int32_t o_ol_cnt; + int32_t o_all_local; + char o_entry_d[DATETIME_SIZE+1]; +}; + +struct OrderLine { + static const int MIN_I_ID = 1; + static const int MAX_I_ID = 100000; // Item::NUM_ITEMS + static const int INITIAL_QUANTITY = 5; + static const float MIN_AMOUNT = 0.01f; + static const float MAX_AMOUNT = 9999.99f; + + int32_t ol_o_id; + int32_t ol_d_id; + int32_t ol_w_id; + int32_t ol_number; + int32_t ol_i_id; + int32_t ol_supply_w_id; + int32_t ol_quantity; + float ol_amount; + char ol_delivery_d[DATETIME_SIZE+1]; + char ol_dist_info[Stock::DIST+1]; +}; + +struct NewOrder { + static const int INITIAL_NUM_PER_DISTRICT = 900; + + int32_t no_w_id; + int32_t no_d_id; + int32_t no_o_id; +}; + +struct History { + static const int MIN_DATA = 12; + static const int MAX_DATA = 24; + static const float INITIAL_AMOUNT = 10.00f; + + int32_t h_c_id; + int32_t h_c_d_id; + int32_t h_c_w_id; + int32_t h_d_id; + int32_t h_w_id; + float h_amount; + char h_date[DATETIME_SIZE+1]; + char h_data[MAX_DATA]; +}; + +// Data returned by the "order status" transaction. +struct OrderStatusOutput { + // From customer + int32_t c_id; // unclear if this needs to be returned + float c_balance; + + // From order + int32_t o_id; + int32_t o_carrier_id; + + struct OrderLineSubset { + int32_t ol_i_id; + int32_t ol_supply_w_id; + int32_t ol_quantity; + float ol_amount; + char ol_delivery_d[DATETIME_SIZE+1]; + }; + + std::vector<OrderLineSubset> lines; + + // From customer + char c_first[Customer::MAX_FIRST+1]; + char c_middle[Customer::MIDDLE+1]; + char c_last[Customer::MAX_LAST+1]; + + // From order + char o_entry_d[DATETIME_SIZE+1]; +}; + +struct NewOrderItem { + int32_t i_id; + int32_t ol_supply_w_id; + int32_t ol_quantity; +}; + +struct NewOrderOutput { + float w_tax; + float d_tax; + + // From district d_next_o_id + int32_t o_id; + + float c_discount; + + // TODO: Client can compute this from other values. + float total; + + struct ItemInfo { + static const char BRAND = 'B'; + static const char GENERIC = 'G'; + + int32_t s_quantity; + float i_price; + // TODO: Client can compute this from other values. + float ol_amount; + char brand_generic; + char i_name[Item::MAX_NAME+1]; + }; + + std::vector<ItemInfo> items; + char c_last[Customer::MAX_LAST+1]; + char c_credit[Customer::CREDIT+1]; + + static const int MAX_STATUS = 25; + static const char INVALID_ITEM_STATUS[]; + char status[MAX_STATUS+1]; +}; + +struct PaymentOutput { + // Return entire tuples since Payment requires most of the data. This returns more than + // necessary, but is easy. + Warehouse warehouse; + District district; + Customer customer; +}; + +struct DeliveryOrderInfo { + int32_t d_id; + int32_t o_id; +}; + +// Interface to the TPC-C transaction implementation. +class TPCCDB { +public: + virtual ~TPCCDB() {} + + // Executes the TPC-C "slev" transaction. From the last 20 orders, returns the number of rows in + // the STOCK table that have S_QUANTITY < threshold. See TPC-C 2.8 (page 43). + virtual int stockLevel(int32_t warehouse_id, int32_t district_id, int32_t threshold) = 0; + + // Executes the TPC-C order status transaction. Find the customer's last order and check the + // delivery date of each item on the order. See TPC-C 2.6 (page 36). + virtual void orderStatus(int32_t warehouse_id, int32_t district_id, int32_t customer_id, + OrderStatusOutput* output) = 0; + + // Executes the TPC-C order status transaction. Find the customer's last order and check the + // delivery date of each item on the order. See TPC-C 2.6 (page 36). + virtual void orderStatus(int32_t warehouse_id, int32_t district_id, const char* c_last, + OrderStatusOutput* output) = 0; + + // Executes the TPC-C new order transaction. Enter the new order for customer_id into the + // database. See TPC-C 2.4 (page 27). Returns true if the transaction commits. + virtual bool newOrder(int32_t warehouse_id, int32_t district_id, int32_t customer_id, + const std::vector<NewOrderItem>& items, const char* now, + NewOrderOutput* output) = 0; + + // Executes the TPC-C payment transaction. Add h_amount to the customer's account. + // See TPC-C 2.5 (page 32). + virtual void payment(int32_t warehouse_id, int32_t district_id, int32_t c_warehouse_id, + int32_t c_district_id, int32_t customer_id, float h_amount, const char* now, + PaymentOutput* output) = 0; + + // Executes the TPC-C payment transaction. Add h_amount to the customer's account. + // See TPC-C 2.5 (page 32). + virtual void payment(int32_t warehouse_id, int32_t district_id, int32_t c_warehouse_id, + int32_t c_district_id, const char* c_last, float h_amount, const char* now, + PaymentOutput* output) = 0; + + // Executes the TPC-C delivery transaction. Delivers the oldest undelivered transaction in each + // district in warehouse_id. See TPC-C 2.7 (page 39). + virtual void deli... [truncated message content] |