//--------------------------------------------------------------------- // Algorithmic Conjurings @ http://www.coyotegulch.com // // Itzam - An Embedded Database Engine // // itzam_cpp_exercise.cpp // //--------------------------------------------------------------------- // // Copyright 2004, 2005 Scott Robert Ladd // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the // Free Software Foundation, Inc. // 59 Temple Place - Suite 330 // Boston, MA 02111-1307, USA. // //----------------------------------------------------------------------- // // For more information on this software package, please visit // Scott's web site, Coyote Gulch Productions, at: // // http://www.coyotegulch.com // //----------------------------------------------------------------------- // itzam #include "../libitzam_cpp/simple_database.h" #include "../libitzam_cpp/complex_database.h" using namespace itzam; // Standard C #include #include #include #if defined(_MSC_VER) && (_MSC_VER < 1400) #define snprintf _snprintf #elif defined(_MSC_VER) && (_MSC_VER >= 1400) #define snprintf sprintf_s #else #include #include #endif // Standard C++ #include #include #include using namespace std; //---------------------------------------------------------- // What the database holds typedef itzam::numeric_key key_type; typedef itzam::atomic_record rec_type; //------------------------------------------------------------------------------- // selector for odd keys class select_odd : public itzam::selector { public: //! Selection function /*! Returns true for any given key. \param key Key to be tested for inclusion in the associated iterator \return Always true */ static bool select(const void * key) { return (*((int64_t *)(key)) & 1); } }; //------------------------------------------------------------------------------- // Database and iterator types used by this program #if defined(NOMUTEX) typedef itzam::simple_database database_t; typedef itzam::simple_iterator, itzam::null_mutex > alliter_t; typedef itzam::simple_iterator odditer_t; #else typedef itzam::simple_database database_t; typedef itzam::simple_iterator > alliter_t; typedef itzam::simple_iterator odditer_t; #endif //---------------------------------------------------------- // list of greek letter names, used for string index with dupes const char * greek_keys [] = { "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega" }; //---------------------------------------------------------- // Classed defining the test database static const itzam_int MATRIX_COLS = 1701; static const itzam_int MATRIX_ROWS = 42; static const itzam_int MATRIX_SIZE = MATRIX_COLS * MATRIX_ROWS; class test_database : public database_file { private: // indexes btree_index m_index_main; btree_index_with_duplicates m_index_greek; matrix_index<> m_index_matrix; public: // Constructor test_database(const string & data_name, const string & main_index_name, const string & greek_index_name, const string & matrix_index_name) : database_file(data_name), m_index_main(main_index_name), m_index_greek(greek_index_name), m_index_matrix(matrix_index_name,MATRIX_COLS,MATRIX_ROWS) { // everything handled by other constructors } // destructor ~test_database() { // everything handled by implied destructors } // Check key existence bool exists(const key_type & key) { bool result = false; database_error error = DB_ERROR_NONE; database_file::lock(); switch (database_object::m_state) { case ITZAM_DB_OPEN: result = m_index_main.exists(key); break; case ITZAM_DB_CLOSED: error = DB_ERROR_READ_CLOSED; break; default: error = DB_ERROR_READ_BROKEN; } database_file::unlock(); if (error != DB_ERROR_NONE) throw database_exception(error); return result; } // Write a record void write(const rec_type & record) { database_error error = DB_ERROR_NONE; database_file::lock(); switch (database_object::m_state) { case ITZAM_DB_OPEN: { // write data record serial_data raw_rec = serial_data(record); itzam_ref ref = itzam_datafile_write(&m_datafile, raw_rec.get_data(), raw_rec.get_size(), ITZAM_NULL_REF); // cout << "(ref " << ref << ") " << flush; if (ref == ITZAM_NULL_REF) error = DB_ERROR_WRITE_FAILED; else { m_index_main.insert(key_type(int64_t(record)),ref); m_index_greek.insert(string_key(greek_keys[int64_t(record) % 24]),ref); itzam_int col = int64_t(record) % MATRIX_COLS; itzam_int row = int64_t(record) % MATRIX_ROWS; if (m_index_matrix.exists(col,row)) m_index_matrix.remove(col,row); else m_index_matrix.insert(col,row,ref); } break; } case ITZAM_DB_CLOSED: error = DB_ERROR_WRITE_CLOSED; break; default: error = DB_ERROR_WRITE_BROKEN; } database_file::unlock(); if (error != DB_ERROR_NONE) throw database_exception(error); } // Read a record rec_type read(const key_type & key) { void * raw_rec; itzam_int rec_len; database_error error = DB_ERROR_NONE; database_file::lock(); switch (database_object::m_state) { case ITZAM_DB_OPEN: { // read record based on reference associated with key itzam_ref ref = m_index_main.find(key); if (ref == ITZAM_NULL_REF) error = DB_ERROR_READ_NOT_FOUND; else { // find record in file itzam_state state = itzam_datafile_seek(&m_datafile, ref); if (state == ITZAM_OKAY) { // read raw record state = itzam_datafile_read_alloc(&m_datafile, &raw_rec, &rec_len); if (state != ITZAM_OKAY) { m_state = ITZAM_DB_BROKEN; error = DB_ERROR_READ_FAILED; } } else error = DB_ERROR_READ_FAILED; } break; } case ITZAM_DB_CLOSED: error = DB_ERROR_READ_CLOSED; break; default: error = DB_ERROR_READ_BROKEN; } if (error != DB_ERROR_NONE) throw database_exception(error); rec_type result(serial_data(raw_rec,rec_len,true)); database_file::unlock(); return result; } //! remove records associated with dupe-supporting index vector read_all(const string & key) { database_error error = DB_ERROR_NONE; itzam_state state = ITZAM_UNKNOWN; vector result; set refs = m_index_greek.find(string_key(key)); for (set::iterator r = refs.begin(); r != refs.end(); ++r) { // find record in file itzam_state state = itzam_datafile_seek(&m_datafile, *r); if (state == ITZAM_OKAY) { void * raw_rec; itzam_int rec_len; // read the record state = itzam_datafile_read_alloc(&m_datafile, &raw_rec, &rec_len); if (state == ITZAM_OKAY) { rec_type record(serial_data(raw_rec,rec_len,true)); result.push_back(record); } else error = DB_ERROR_READ_FAILED; } else error = DB_ERROR_READ_FAILED; } if (error != DB_ERROR_NONE) throw database_exception(error); return result; } // Read a record rec_type read(itzam_int col, itzam_int row) { void * raw_rec; itzam_int rec_len; database_error error = DB_ERROR_NONE; database_file::lock(); switch (database_object::m_state) { case ITZAM_DB_OPEN: { // read record based on reference associated with key itzam_ref ref = m_index_matrix.find(col,row); if (ref == ITZAM_NULL_REF) error = DB_ERROR_READ_NOT_FOUND; else { // find record in file itzam_state state = itzam_datafile_seek(&m_datafile, ref); if (state == ITZAM_OKAY) { // read raw record state = itzam_datafile_read_alloc(&m_datafile, &raw_rec, &rec_len); if (state != ITZAM_OKAY) { m_state = ITZAM_DB_BROKEN; error = DB_ERROR_READ_FAILED; } } else error = DB_ERROR_READ_FAILED; } break; } case ITZAM_DB_CLOSED: error = DB_ERROR_READ_CLOSED; break; default: error = DB_ERROR_READ_BROKEN; } database_file::unlock(); if (error != DB_ERROR_NONE) throw database_exception(error); rec_type result(serial_data(raw_rec,rec_len,true)); return result; } // Remove records by primary key void remove(const key_type & key) { database_error error = DB_ERROR_NONE; database_file::lock(); switch (database_object::m_state) { case ITZAM_DB_OPEN: { // read record based on reference associated with key itzam_ref ref = m_index_main.find(key); if (ref == ITZAM_NULL_REF) error = DB_ERROR_READ_NOT_FOUND; else { // find record in file itzam_state state = itzam_datafile_seek(&m_datafile, ref); if (state == ITZAM_OKAY) { // remove record from datafile // cout << "(ref " << ref << ") " << flush; state = itzam_datafile_remove(&m_datafile); if (state == ITZAM_OKAY) { // remove record from each index by key m_index_main.remove(key); m_index_greek.remove_ref(string_key(greek_keys[uint64_t(key) % 24]),ref); itzam_int col = int64_t(key) % MATRIX_COLS; itzam_int row = int64_t(key) % MATRIX_ROWS; if (m_index_matrix.exists(col,row)) m_index_matrix.remove(col,row); } else { m_state = ITZAM_DB_BROKEN; error = DB_ERROR_READ_FAILED; } } else error = DB_ERROR_READ_FAILED; } break; } case ITZAM_DB_CLOSED: error = DB_ERROR_READ_CLOSED; break; default: error = DB_ERROR_READ_BROKEN; } database_file::unlock(); if (error != DB_ERROR_NONE) throw database_exception(error); } }; //---------------------------------------------------------- // Display program usage static const char * USAGE = "\nusage: complex_test [options]\n\n" "options:\n" " -simple test the simple_database classes (default)\n" " -complex test the complex_database classes\n" " -tests n number of test iterations to run (default = 10000)\n" " -maxkey n maximum key value (default = 10000)\n" " -noremove do not remove keys (add only)\n" " -verify verify database contents after every operation (implies verbose, slow)\n" " -seed n use n as the random number seed (default is random)\n" " -list at the end, list the database via the secondary index\n" " -verbose display progress text\n" " -help this usage information\n" "\n"; static void show_usage() { cout << USAGE << endl; } //---------------------------------------------------------- // embedded random number generator; ala Park and Miller // return number within a given range static int32_t seed = 1325; static int32_t random_int32(int32_t limit) { static const int32_t IA = 16807; static const int32_t IM = 2147483647; static const int32_t IQ = 127773; static const int32_t IR = 2836; static const int32_t MASK = 123459876; int32_t k; int32_t result; seed ^= MASK; k = seed / IQ; seed = IA * (seed - k * IQ) - IR * k; if (seed < 0L) seed += IM; result = (seed % limit); seed ^= MASK; return result; } //---------------------------------------------------------- // Verifies that the database contains the expected records static void simple_verify(database_t & db, bool * key_flags, int max_key) { int k; key_type key; rec_type rec; // make sure check values match btree cout << " -- verifying... " << flush; for (k = 0; k < max_key; ++k) { key = key_type(k); if (db.exists(key)) { if (!key_flags[k]) { cout << "key found, and should not have been" << endl; exit(EXIT_FAILURE); } try { rec = db.read(key); } catch (itzam::database_exception & ex) { cout << "unable to read existing record" << endl; exit(EXIT_FAILURE); } if (int64_t(rec) != k) { cout << "data does not match key" << endl; exit(EXIT_FAILURE); } } else { if (key_flags[k]) { cout << "expected key not found" << endl; exit(EXIT_FAILURE); } } } cout << "okay" << endl; } //---------------------------------------------------------- // Verifies that the database contains the expected records static void complex_verify(test_database & db, bool * key_flags, int max_key) { int64_t k; size_t count1 = 0; size_t count2 = 0; // make sure check values match main btree cout << " -- verifying... " << flush; for (k = 0; k < max_key; ++k) { key_type key(k); if (db.exists(key)) { if (!key_flags[k]) { cout << "key found, and should not have been" << endl; exit(EXIT_FAILURE); } try { rec_type rec = db.read(key); if (rec != k) { cout << "data does not match key" << endl; exit(EXIT_FAILURE); } } catch (database_exception & ex) { cout << "unable to read existing record" << endl; exit(EXIT_FAILURE); } ++count1; } else { if (key_flags[k]) { cout << "expected key not found" << endl; exit(EXIT_FAILURE); } } } // now check the secondary index for (k = 0; k < 24; ++k) { vector records = db.read_all(greek_keys[k]); for (vector::iterator r = records.begin(); r != records.end(); ++r) { if (!key_flags[int64_t(*r)]) { cout << "invalid key found in secondary index" << endl; exit(EXIT_FAILURE); } } count2 += records.size(); } // now check matrix index for (itzam_int row = 0; row < MATRIX_ROWS; ++row) { // cout << "matrix contains records at" << endl; for (itzam_int col = 0; col < MATRIX_COLS; ++col) { try { // we're just checking to see if we can read the record rec_type rec = db.read(col,row); // does the record belong at these coordinates? if ((col != int64_t(rec) % MATRIX_COLS) || (row != int64_t(rec) % MATRIX_ROWS)) { cout << "record " << int64_t(rec) << " does not belong at (" << col << "," << row << ")" << endl; exit(EXIT_FAILURE); } } catch (database_exception & ex) { if (ex.get_error() != DB_ERROR_READ_NOT_FOUND) { cout << "matrix read error" << endl; exit(EXIT_FAILURE); } } } } if (count1 != count2) { cout << "index1 has " << count1 << " while index2 has " << count2 << " records" << endl; exit(EXIT_FAILURE); } cout << "okay" << endl; } void list_greek(test_database & db) { // display records in the secondary index cout << "\n\nsecondary index listing:\n" << endl; for (size_t k = 0; k < 24; ++k) { cout << setw(7) << greek_keys[k] << ":"; vector records = db.read_all(greek_keys[k]); if (records.size() > 0) { for (vector::iterator r = records.begin(); r != records.end(); ++r) cout << " " << int64_t(*r); } else cout << " empty"; cout << endl; } } //------------------------------------------------------------------------------- // entry point and main program int main(int argc, char * argv[]) { // working storage int i; // operational parameters int64_t max_key = 10000; size_t test_size = 10000; bool remove = true; bool verified_run = false; bool iter_list = false; bool verbose = false; bool simple_test = true; const char * prog = "|/-\\"; seed = (long)time(NULL); // parse arguments for (i = 1; i < argc; ++i) { if (0 == strcmp(argv[i],"-verify")) { verbose = true; verified_run = true; } else if (0 == strcmp(argv[i],"-help")) { show_usage(); exit(EXIT_FAILURE); } else if (0 == strcmp(argv[i],"-list")) { iter_list = true; } else if (0 == strcmp(argv[i],"-simple")) { simple_test = true; } else if (0 == strcmp(argv[i],"-complex")) { simple_test = false; } else if (0 == strcmp(argv[i],"-noremove")) { remove = false; } else if (0 == strcmp(argv[i],"-verbose")) { verbose = true; } else if (0 == strcmp(argv[i],"-seed")) { ++i; if (i < argc) seed = strtol(argv[i],NULL,10); } else if (0 == strcmp(argv[i],"-tests")) { ++i; if (i < argc) test_size = strtoul(argv[i],NULL,10); } else if (0 == strcmp(argv[i],"-maxkey")) { ++i; if (i < argc) max_key = strtoul(argv[i],NULL,10); } } // describe parameters of the run printf("number of tests..... %lu\nmaximum key value... %lu\nrandom seed......... %ld\n", test_size, max_key, seed); printf("test type........... %s\n\n",simple_test ? "simple" : "complex"); // allocate and initialize array of flags to track which keys are supposed // to be in the database bool * key_flags = new bool[max_key]; for (i = 0; i < (int)max_key; ++i) key_flags[i] = false; // generate a random file name for the database uint32_t file_code = 0; const size_t MAX_FILE_NAME = 256; char data_name[MAX_FILE_NAME]; char index_name1[MAX_FILE_NAME]; char index_name2[MAX_FILE_NAME]; char index_name3[MAX_FILE_NAME]; #if defined(_MSC_VER) file_code = time(NULL); #else // first try to read /dev/urandom int fd = open ("/dev/urandom", O_RDONLY); if (fd != -1) { read(fd, &file_code, 4); close(fd); } else file_code = (uint32_t)time(NULL); #endif // now let's do a little database work try { if (simple_test) { snprintf(data_name,MAX_FILE_NAME,"db%08x.dat",file_code); // create a database object database_t db(data_name); // if the db opened, start working on it if (db.is_open()) { for (size_t n = 1; n <= test_size; ++n) { // data number to be written; acts as both key and data (ref) int64_t k = (int64_t)random_int32((size_t)max_key); // print info if (verbose) cout << setw(8) << n << ": test key = " << setw(8) << k << ": "; else { if (0 == n % 1000) cout << "\b. " << flush; else cout << "\b" << prog[n % 4] << flush; } // create key and record, the same for verification porposes key_type key(k); rec_type rec(k); // does this key already exist? if (db.exists(key)) { if (!key_flags[size_t(k)]) { cout << "this key was found, and should not have been" << endl; exit(EXIT_FAILURE); } else { if (remove) { // remove a record db.remove(key); key_flags[size_t(k)] = false; if (verbose) cout << "removed successfully"; } else { if (verbose) cout << "duplicate ignored"; } } } else { if (key_flags[size_t(k)]) { cout << "this key was not found, and should have been" << endl; exit(EXIT_FAILURE); } else { // write a record db.write(key,rec); key_flags[size_t(k)] = true; if (verbose) cout << "written successfully"; } } // verify every operation, if so requested if (verified_run) simple_verify(db,key_flags,max_key); else if (verbose) cout << " -- done" << endl; } // close database anmd reopen it db.close(); db.open(); // verify that database holds what we think it holds cout << "\n\ndatabase check "; simple_verify(db,key_flags,max_key); // look through the database using iterators if (iter_list) { alliter_t iter1(db); if (iter1.is_valid()) { cout << "\nforward iterator, all records" << endl; do { rec_type rec = iter1.get_record(); cout << "read " << setw(8) << int64_t(rec) << endl; } while (++iter1); // backward, odd only odditer_t iter2(db); iter2.move_last(); cout << "\nbackward iterator, odd records only" << endl; do { rec_type rec = iter2.get_record(); cout << "read " << setw(8) << int64_t(rec) << endl; } while (--iter2); } } db.close(); } } else { snprintf(data_name,MAX_FILE_NAME,"db%08x.data",file_code); snprintf(index_name1,MAX_FILE_NAME,"db%08x.idx1",file_code); snprintf(index_name2,MAX_FILE_NAME,"db%08x.idx2",file_code); snprintf(index_name3,MAX_FILE_NAME,"db%08x.idx3",file_code); // create a database object test_database db(data_name,index_name1,index_name2,index_name3); // if the db opened, start working on it if (db.is_open()) { for (size_t n = 0; n < test_size; ++n) { // key and record int64_t k = (int64_t)random_int32((size_t)max_key); key_type key(k); rec_type rec(k); // print info if (verbose) cout << setw(8) << n << ": test key = " << setw(8) << k << ", " << setw(7) << greek_keys[k % 24] << ": "; else { if (0 == n % 1000) cout << "\b. " << flush; else cout << "\b" << prog[n % 4] << flush; } // does this key already exist? if (db.exists(key)) { if (!key_flags[size_t(k)]) { cout << "this key was found, and should not have been" << endl; exit(EXIT_FAILURE); } else { if (remove) { // remove a record db.remove(key); key_flags[size_t(k)] = false; if (verbose) cout << "removed successfully"; } else { if (verbose) cout << "duplicate ignored"; } } } else { if (key_flags[size_t(k)]) { cout << "this key was not found, and should have been" << endl; exit(EXIT_FAILURE); } else { // write a record db.write(rec); key_flags[size_t(k)] = true; if (verbose) cout << "written successfully"; } } // verify every operation, if so requested if (verified_run) complex_verify(db,key_flags,max_key); else if (verbose) cout << " -- done" << endl; } // close and reopen database db.close(); db.open(); // verify that database holds what we think it holds cout << "\n\ndatabase check "; complex_verify(db,key_flags,max_key); // close database db.close(); } // list database by greek key if (iter_list) { db.open(); list_greek(db); db.close(); } } } catch (database_exception & ex) { cout << ex.what() << endl; exit(EXIT_FAILURE); } // database will be implicitly closed by its destructor delete [] key_flags; // we're done return EXIT_SUCCESS; }