Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
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, &parameters[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,
&parameters[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, &parameters[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