Skip to content

Instantly share code, notes, and snippets.

Embed
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
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.