Building an embedded column store database with upscaledb - part 3
/* | |
* 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