#pragma once #include "fsized_map.h" #include #include enum class data_mode { A, B, AB }; struct metadata_t{ bitops::regulated record_cnt; bitops::regulated page_cnt; bitops::regulated delete_cnt; bitops::regulated last_page; bitops::regulated last_delete; }; class database { database(){} public: mmap_array, record>> records; mmap_array pages; mmap_array metadata; mmap_array delete_table; database( const std::string& records, const std::string& pages, const std::string& deletions, const std::string& meta, const size_t& record_cnt = 4096, const size_t& page_cnt = 4096, const size_t& delete_cnt = 512 ) : records{record_cnt, records} , pages{page_cnt, pages} , metadata{(size_t)1, meta} , delete_table{delete_cnt, deletions} {} static void mark_empty_page(db_page& gojp, size_t idx) { auto& gojmark = *(std::array, db_page_size/sizeof(uint32_t)>*)&gojp; bitops::regulated mark = 0; std::array mark_array = {'G', 'o', 'J', '@'}; mark.internal = *(uint32_t*)&mark_array; uint32_t slice = static_cast(idx); for(size_t mpos = 0; mpos < gojmark.size(); mpos+=2) { gojmark[mpos] = mark; gojmark[mpos+1] = slice++; } } static database&& create(const std::string dir, size_t page_nb) { database ret{ dir+"records", dir+"pages", dir+"deleted", dir+"meta", page_nb+page_nb/2, page_nb, page_nb/8 }; std::cout << "Record table: "; for(auto& n : ret.records) { n.second.timestamp = 0; n.second.offset = 0; n.second.flags = 0; } std::cout << "DONE" << std::endl; std::cout << "Delete table: "; for(auto& n : ret.delete_table) { n = std::numeric_limits::max(); } std::cout << "DONE" << std::endl; for(size_t idx = 0; idx < ret.pages.size(); idx++) { mark_empty_page(ret.pages[idx], idx); if(idx % 120000 == 0) { std::cout << "Page markings: " << uint16_t(100.0f*float(idx)/float(ret.pages.size())) << "%" << std::endl; } } std::cout << "Page markings: DONE" << std::endl; (*ret.metadata).last_page = 0; (*ret.metadata).last_delete = 0; (*ret.metadata).record_cnt = page_nb+page_nb/2; (*ret.metadata).page_cnt = page_nb; (*ret.metadata).delete_cnt = page_nb/8; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wreturn-local-addr" return std::move(ret); #pragma GCC diagnostic pop } static database&& open(const std::string dir) { mmap_array tmp{(size_t)1, dir+"meta"}; database ret{ dir+"records", dir+"pages", dir+"deleted", dir+"meta", (*tmp).record_cnt, (*tmp).page_cnt, (*tmp).delete_cnt }; tmp.clear(); ret.records.enforce_caching(); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wreturn-local-addr" return std::move(ret); #pragma GCC diagnostic pop } /************************************************************************** * * * NO CONFIRM OPS * * * * * *************************************************************************/ record write(const record_identifier& target, const db_page& value){ uint64_t page = std::numeric_limits::max();; size_t off = std::numeric_limits::max(); if(metadata[0].last_delete>0) { off = (*metadata).last_delete; page = delete_table[off-1]; } else { page = (*metadata).last_page; if(page>=pages.size()) { throw std::runtime_error("PAGE STARVATION! MUST EXIT NOW"); } } if(page == std::numeric_limits::max()) { throw std::runtime_error("PAGE ERROR! MUST EXIT NOW"); } pages[page] = value; uint64_t hashed = std::hash{}(target); uint64_t hashed_roll = hashed; bool succeed = false; uint64_t ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); std::pair,record> tmp{0, record{}}; tmp.first = hashed; tmp.second.record_head.split = target; tmp.second.timestamp = ts; tmp.second.offset = page; tmp.second.flags = (uint32_t)record_flags::confirmation; do{ uint64_t pos = hashed_roll % records.size(); switch (static_cast(records[pos].second.timestamp)) { case 0: [[fallthrough]]; case std::numeric_limits::max(): records[pos] = tmp; succeed = true; break; default: break; } hashed_roll++; }while(!succeed); if(off != std::numeric_limits::max()) { (*metadata).last_delete += -1; delete_table[off] = std::numeric_limits::max(); } else { (*metadata).last_page += (size_t)1; } return tmp.second; } std::pair read(const record_identifier& target) { std::pair ret; ret.second.fill(0); uint64_t hashed = std::hash{}(target); uint64_t hashed_roll = hashed; do{ uint64_t pos = hashed_roll % records.size(); auto& value = records[pos].second; switch (static_cast(value.timestamp)) { case 0: return ret; case std::numeric_limits::max(): break; default: if(records[pos].first == hashed) if(std::hash{}(value.record_head.split) == hashed) { if(ret.first.timestamp{}(target); uint64_t hashed_roll = hashed; do{ uint64_t pos = hashed_roll % records.size(); auto& value = records[pos].second; switch (static_cast(value.timestamp)) { case 0: return; case std::numeric_limits::max(): break; default: if(records[pos].first == hashed) if(std::hash{}(value.record_head.split) == hashed) { value.timestamp = std::numeric_limits::max(); (*metadata).last_delete+=1; delete_table[(*metadata).last_delete-1] = value.offset; mark_empty_page(pages[value.offset], value.offset); value.offset = 0; } break; } hashed_roll++; }while(true); // return only happens on hitting a case 0 } void rollback(const record_identifier&) { } /************************************************************************** * * * CONFIRM OPS * * * * * *************************************************************************/ record stepped_write(const record_identifier& target, const db_page& value){ uint64_t page = std::numeric_limits::max();; size_t off = std::numeric_limits::max(); if(metadata[0].last_delete>0) { off = (*metadata).last_delete; page = delete_table[off-1]; } else { page = (*metadata).last_page; if(page>=pages.size()) { throw std::runtime_error("PAGE STARVATION! MUST EXIT NOW"); } } if(page == std::numeric_limits::max()) { throw std::runtime_error("PAGE ERROR! MUST EXIT NOW"); } pages[page] = value; uint64_t hashed = std::hash{}(target); uint64_t hashed_roll = hashed; bool succeed = false; uint64_t ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); std::pair,record> tmp{0, record{}}; tmp.first = hashed; tmp.second.record_head.split = target; tmp.second.timestamp = ts; tmp.second.offset = page; tmp.second.flags = 0; do{ uint64_t pos = hashed_roll % records.size(); switch (static_cast(records[pos].second.timestamp)) { case 0: [[fallthrough]]; case std::numeric_limits::max(): records[pos] = tmp; succeed = true; break; default: break; } hashed_roll++; }while(!succeed); if(off != std::numeric_limits::max()) { (*metadata).last_delete += -1; delete_table[off] = std::numeric_limits::max(); } else { (*metadata).last_page += (size_t)1; } return tmp.second; } record try_allocate(const record_identifier& target) { auto attempt = read(target); if(attempt.first.timestamp == 0) { db_page rnd_page; { std::random_device dev; std::minstd_rand temprng(dev()); auto tmp = (std::array*)&rnd_page; std::generate(tmp->begin(), tmp->end(), temprng); } return write(target, rnd_page); } return record{}; } std::pair stepped_read(const record_identifier& target) { std::pair ret; ret.second.fill(0); uint64_t hashed = std::hash{}(target); uint64_t hashed_roll = hashed; do{ uint64_t pos = hashed_roll % records.size(); auto& value = records[pos].second; switch (static_cast(value.timestamp)) { case 0: return ret; case std::numeric_limits::max(): break; default: if(records[pos].first == hashed) if(std::hash{}(value.record_head.split) == hashed) { if(static_cast(value.flags) & (uint32_t)record_flags::confirmation) if(ret.first.timestamp& timestamp) { uint64_t hashed = std::hash{}(target); uint64_t hashed_roll = hashed; do{ uint64_t pos = hashed_roll % records.size(); auto& value = records[pos].second; switch (static_cast(value.timestamp)) { case 0: return; case std::numeric_limits::max(): break; default: if(records[pos].first == hashed) if(std::hash{}(value.record_head.split) == hashed) { if(timestamp == value.timestamp) { value.flags = (uint32_t)value.flags | (uint32_t)record_flags::confirmation; } break; } } hashed_roll++; }while(true); } };