Created
February 10, 2016 13:57
-
-
Save cruppstahl/df2e2f98bc3c8b8f6822 to your computer and use it in GitHub Desktop.
Building an embedded column store database with upscaledb - part 3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* This code is in the public domain. | |
*/ | |
/* | |
* This code is for "Building an embedded column store database with | |
* upscaledb - part 3" | |
* http://upscaledb.com/blog/0008-building-an-embedded-column-store-database-part3.html | |
*/ | |
#include <iostream> | |
#include <assert.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <time.h> | |
#include <ups/upscaledb.h> | |
#define MAX 64 | |
static ups_env_t *env; | |
static ups_db_t *db_primary; | |
static ups_db_t *dbi_username; | |
static ups_db_t *dbi_dayofbirth; | |
static ups_db_t *dbi_compound; | |
/* Fixed length arrays are wasteful, and the "day_of_birth" value might not | |
* be large enough if your day of birth is too high (< 1.1.1970). | |
* It's good enough for this example, though. */ | |
struct UserInformation { | |
char username[MAX]; | |
char password[MAX]; | |
char email[MAX]; | |
uint32_t day_of_birth; // time_t: seconds since 1.1.1970 | |
}; | |
struct CompoundIndex { | |
uint32_t day_of_birth; // time_t: seconds since 1.1.1970 | |
char username[MAX]; | |
}; | |
static void | |
handle_error(const char *foo, ups_status_t st) | |
{ | |
std::cout << foo << "() returned error " << st << ": " | |
<< ups_strerror(st) << std::endl; | |
::exit(-1); | |
} | |
static ups_env_t * | |
create_environment() | |
{ | |
ups_env_t *env; | |
ups_status_t st = ups_env_create(&env, "tutorial.db", | |
UPS_ENABLE_TRANSACTIONS, 0664, 0); | |
if (st != UPS_SUCCESS) | |
handle_error("ups_env_create", st); | |
return env; | |
} | |
static ups_db_t * | |
create_primary_database(ups_env_t *env) | |
{ | |
ups_db_t *db; | |
ups_status_t st = ups_env_create_db(env, &db, 1, UPS_RECORD_NUMBER32, 0); | |
if (st != UPS_SUCCESS) | |
handle_error("ups_env_create_db", st); | |
return db; | |
} | |
static ups_db_t * | |
create_username_index(ups_env_t *env) | |
{ | |
ups_db_t *db; | |
ups_parameter_t parameters[] = { | |
{UPS_PARAM_KEY_SIZE, MAX}, | |
{UPS_PARAM_RECORD_SIZE, 4}, | |
{0, 0} | |
}; | |
ups_status_t st = ups_env_create_db(env, &db, 2, 0, ¶meters[0]); | |
if (st != UPS_SUCCESS) | |
handle_error("ups_env_create_db", st); | |
return db; | |
} | |
static ups_db_t * | |
create_dayofbirth_index(ups_env_t *env) | |
{ | |
ups_db_t *db; | |
ups_parameter_t parameters[] = { | |
{UPS_PARAM_KEY_TYPE, UPS_TYPE_UINT32}, | |
{UPS_PARAM_RECORD_SIZE, 4}, | |
{0, 0} | |
}; | |
ups_status_t st = ups_env_create_db(env, &db, 3, UPS_ENABLE_DUPLICATES, | |
¶meters[0]); | |
if (st != UPS_SUCCESS) | |
handle_error("ups_env_create_db", st); | |
return db; | |
} | |
static int | |
compare_compound_index(ups_db_t *db, | |
const uint8_t *lhs_data, uint32_t lhs_size, | |
const uint8_t *rhs_data, uint32_t rhs_size) | |
{ | |
assert(lhs_size == sizeof(CompoundIndex)); | |
assert(rhs_size == sizeof(CompoundIndex)); | |
const CompoundIndex *lhs = (const CompoundIndex *)lhs_data; | |
const CompoundIndex *rhs = (const CompoundIndex *)rhs_data; | |
if (lhs->day_of_birth < rhs->day_of_birth) | |
return -1; | |
if (lhs->day_of_birth > rhs->day_of_birth) | |
return +1; | |
// both are equal - compare usernames | |
return ::strncmp(lhs->username, rhs->username, sizeof(lhs->username)); | |
} | |
static ups_db_t * | |
create_compound_index(ups_env_t *env) | |
{ | |
ups_db_t *db; | |
ups_parameter_t parameters[] = { | |
{UPS_PARAM_KEY_TYPE, UPS_TYPE_CUSTOM}, | |
{UPS_PARAM_KEY_SIZE, sizeof(CompoundIndex)}, | |
{UPS_PARAM_CUSTOM_COMPARE_NAME, (uint64_t)"CompoundIndex-compare"}, | |
{UPS_PARAM_RECORD_SIZE, 4}, | |
{0, 0} | |
}; | |
/* register the compare function */ | |
ups_status_t st = ups_register_compare("CompoundIndex-compare", | |
compare_compound_index); | |
if (st != UPS_SUCCESS) | |
handle_error("ups_register_compare", st); | |
st = ups_env_create_db(env, &db, 4, 0, ¶meters[0]); | |
if (st != UPS_SUCCESS) | |
handle_error("ups_env_create_db", st); | |
return db; | |
} | |
static uint32_t | |
add_new_user(const char *username, const char *password, const char *email, | |
uint32_t day_of_birth) | |
{ | |
UserInformation ui = {0}; | |
::strncpy(ui.username, username, sizeof(ui.username)); | |
::strncpy(ui.password, password, sizeof(ui.password)); | |
::strncpy(ui.email, email, sizeof(ui.email)); | |
ui.day_of_birth = day_of_birth; | |
/* this uses a transaction to rollback in case of an error */ | |
ups_txn_t *txn; | |
ups_status_t st = ups_txn_begin(&txn, env, 0, 0, 0); | |
if (st != UPS_SUCCESS) | |
handle_error("ups_txn_begin", st); | |
/* update the primary database */ | |
ups_key_t key = {0}; | |
ups_record_t record = ups_make_record(&ui, sizeof(ui)); | |
st = ups_db_insert(db_primary, 0, &key, &record, 0); | |
if (st != UPS_SUCCESS) { | |
ups_txn_abort(txn, 0); | |
handle_error("ups_db_insert (primary)", st); | |
} | |
/* retrieve the generated user id - this is our return value */ | |
assert(key.size == sizeof(uint32_t)); | |
uint32_t user_id = *(uint32_t *)key.data; | |
/* update the secondary index with the usernames */ | |
ups_key_t kusername = ups_make_key(ui.username, sizeof(ui.username)); | |
ups_record_t rusername = ups_make_record(&user_id, sizeof(user_id)); | |
st = ups_db_insert(dbi_username, 0, &kusername, &rusername, 0); | |
if (st != UPS_SUCCESS) { | |
ups_txn_abort(txn, 0); | |
handle_error("ups_db_insert (username index)", st); | |
} | |
/* update the secondary index with the day of birth */ | |
ups_key_t kdayofbirth = ups_make_key(&ui.day_of_birth, | |
sizeof(ui.day_of_birth)); | |
ups_record_t rdayofbirth = ups_make_record(&user_id, sizeof(user_id)); | |
st = ups_db_insert(dbi_dayofbirth, 0, &kdayofbirth, &rdayofbirth, | |
UPS_DUPLICATE); | |
if (st != UPS_SUCCESS) { | |
ups_txn_abort(txn, 0); | |
handle_error("ups_db_insert (day_of_birth index)", st); | |
} | |
/* update the secondary index with the compound key of day of birth | |
* and username */ | |
CompoundIndex compound = {0}; | |
compound.day_of_birth = ui.day_of_birth; | |
::strncpy(compound.username, ui.username, sizeof(ui.username)); | |
ups_key_t kcompound = ups_make_key(&compound, sizeof(compound)); | |
ups_record_t rcompound = ups_make_record(&user_id, sizeof(user_id)); | |
st = ups_db_insert(dbi_compound, 0, &kcompound, &rcompound, 0); | |
if (st != UPS_SUCCESS) { | |
ups_txn_abort(txn, 0); | |
handle_error("ups_db_insert (compound index)", st); | |
} | |
st = ups_txn_commit(txn, 0); | |
if (st != UPS_SUCCESS) | |
handle_error("ups_txn_commit", st); | |
return user_id; | |
} | |
static uint32_t | |
make_epoch_timestamp(int day, int month, int year) | |
{ | |
struct tm timeinfo = {0}; | |
timeinfo.tm_year = year - 1900; | |
timeinfo.tm_mon = month - 1; | |
timeinfo.tm_mday = day; | |
return (uint32_t)::mktime(&timeinfo); | |
} | |
static bool | |
is_username_unique(const char *username) | |
{ | |
char keyname[MAX] = {0}; | |
::strncpy(keyname, username, sizeof(keyname)); | |
ups_key_t key = ups_make_key(keyname, sizeof(keyname)); | |
ups_record_t record = {0}; | |
ups_status_t st = ups_db_find(dbi_username, 0, &key, &record, 0); | |
if (st == UPS_SUCCESS) | |
return false; | |
if (st != UPS_KEY_NOT_FOUND) | |
handle_error("ups_db_find", st); | |
return true; | |
} | |
struct InsertData { | |
uint32_t user_id; | |
const char *username; | |
const char *password; | |
const char *email; | |
int day; | |
int month; | |
int year; | |
}; | |
static InsertData insert_data[] = { | |
{0, "tom", "s3cr3t", "batman@host.com", 10, 10, 1977}, | |
{0, "amy", "AS302.x", "amy@server.com", 1, 1, 1970}, | |
{0, "rick", "kcir", "the_rick@email.net", 1, 1, 1985}, | |
{0, "aaa10", "ppp10", "mmm10@email.com", 1, 1, 1980}, | |
{0, "aaa11", "ppp11", "mmm11@email.com", 1, 1, 1981}, | |
{0, "aaa12", "ppp12", "mmm12@email.com", 1, 1, 1982}, | |
{0, "aaa13", "ppp13", "mmm13@email.com", 1, 1, 1983}, | |
{0, "aaa14", "ppp14", "mmm14@email.com", 1, 1, 1984}, | |
{0, "aaa15", "ppp15", "mmm15@email.com", 1, 1, 1985}, | |
{0, "aaa16", "ppp16", "mmm16@email.com", 1, 1, 1986}, | |
{0, "aaa17", "ppp17", "mmm17@email.com", 1, 1, 1987}, | |
{0, "aaa18", "ppp18", "mmm18@email.com", 1, 1, 1988}, | |
{0, "aaa19", "ppp19", "mmm19@email.com", 1, 1, 1989}, | |
{0, "aaa20", "ppp20", "mmm20@email.com", 1, 1, 1990}, | |
{0, "aaa21", "ppp21", "mmm21@email.com", 1, 1, 1991}, | |
{0, "aaa22", "ppp22", "mmm22@email.com", 1, 1, 1992}, | |
{0, "aaa23", "ppp23", "mmm23@email.com", 1, 1, 1993}, | |
{0, "aaa24", "ppp24", "mmm24@email.com", 1, 1, 1994}, | |
{0, "aaa25", "ppp25", "mmm25@email.com", 1, 1, 1995}, | |
{0, "aaa26", "ppp26", "mmm26@email.com", 1, 1, 1996}, | |
{0, "aaa27", "ppp27", "mmm27@email.com", 1, 1, 1997}, | |
{0, "aaa28", "ppp28", "mmm28@email.com", 1, 1, 1998}, | |
{0, "aaa29", "ppp29", "mmm29@email.com", 1, 1, 1999}, | |
{0, 0, 0, 0, 0, 0, 0} | |
}; | |
int | |
main(int argc, char **argv) | |
{ | |
/* First create a new upscaledb Environment */ | |
env = create_environment(); | |
/* Then create the primary Database; key is the (automatically assigned) | |
* user ID, record is a struct UserInformation */ | |
db_primary = create_primary_database(env); | |
/* A secondary index for the usernames */ | |
dbi_username = create_username_index(env); | |
/* A secondary index for the birth dates */ | |
dbi_dayofbirth = create_dayofbirth_index(env); | |
/* A secondary compound index for birth dates and user names */ | |
dbi_compound = create_compound_index(env); | |
/* insert several users into the primary database */ | |
for (InsertData *data = &insert_data[0]; data->username != 0; data++) { | |
data->user_id = add_new_user(data->username, data->password, data->email, | |
make_epoch_timestamp(data->day, data->month, data->year)); | |
std::cout << "inserted user '" << data->username << "': user id is " | |
<< data->user_id << std::endl; | |
} | |
/* testing the username index - 'tom' and 'amy' already exist, | |
* 'alice' and 'chris' don't */ | |
assert(is_username_unique("tom") == false); | |
assert(is_username_unique("amy") == false); | |
assert(is_username_unique("chris") == true); | |
assert(is_username_unique("alice") == true); | |
/* clean up and close the environment with all its databases */ | |
ups_status_t st = ups_env_close(env, UPS_AUTO_CLEANUP); | |
if (st != UPS_SUCCESS) | |
handle_error("ups_env_close", st); | |
std::cout << "success!" << std::endl; | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment