Skip to content

Latest commit

 

History

History
745 lines (693 loc) · 28.4 KB

readme.md

File metadata and controls

745 lines (693 loc) · 28.4 KB

about

sph-db is considered feature complete and in beta as of 2018-10, you can try it and report any issues

  • license: lgpl3+

project goals

  • a (very) fast nosql database as a shared library to store records
  • graph-like many-to-many relations without having to manage junction tables

features

data model

  • records have identifiers for random access and fields of various types and are indexable. records are just like table rows in relational databases
  • records can act as nodes in relations to build a graph
  • relations are directed, labeled, unidirectionally ordered and small

technology

  • acid compliant, memory-mapped database that can grow to any size that fits on the local filesystem, unrestricted by available ram
  • direct, high-speed interface using c data structures. no overhead from sql or similar query language parsing and processing
  • embeddable by linking or code inclusion
  • read-optimised design with full support for parallel database reads
  • efficient through focus on limited feature-set and thin abstraction over lmdb. benchmarks for lmdb can be found here
  • written in c, currently via sc

dependencies

setup

  1. install run-time and quick build dependencies
  2. maybe adjust the compile-time configuration at the beginning of source/c-precompiled/main/sph-db.h. these settings are fixed for compiled shared libraries and the settings in the header must match the values a shared library to use was compiled with
  3. change into the project directory and execute ./exe/compile-c
  4. execute ./exe/install. this supports one optional argument: a path prefix to install to

optionally execute ./exe/test to see if the tests run successful

usage in c

the example code can be found in other/examples/example-usage.c

compilation of programs using sph-db

for example with gcc:

gcc example.c -o example-executable -lsph-db

inclusion of api declarations

#include "<sph-db.h>"

error handling

db routines return a "status_t" object that contains a status and a status-group identifier (error code and source library identifier). bindings to work with this small object are included with the main header "sph-db.h". sph-db internally usually uses a goto label named "exit" per routine where undesired return stati are handled. status_require goes to exit on any failure status (status.id not zero). the following examples assume this pattern of calling status_declare to introduce an initialised variable named status and having a label named exit

int main() {
  status_declare;
  // example code ...
exit:
  return status.id;
}

initialisation

db_env_declare(env);
status_require(db_env_new(&env));
// the directory and database will be created if it does not exist
status_require(db_open("/tmp/example", 0, env));
// code that makes use of the database ...
db_close(&env);

transactions

  • transactions are required for reading and writing. routines for schema changes like type and index creation create a transaction internally and must not be used while another transaction is active in the same thread
  • there must only be one active transaction per thread (there is an option to relax this for read transactions). nested transactions might be possible in the future
  • returned data pointers, for example from db_record_data_ref, are only valid until the corresponding transaction is aborted or committed. data pointers are always only for reading and should never be written to

declare a transaction handle variable

// arguments: db-env-t*, custom_variable_name
db_txn_declare(env, txn);

start a read-only transaction

db_txn_begin(&txn);

start a read-write transaction

db_txn_write_begin(&txn);

finish transaction, discarding any writes

db_txn_abort(&txn);

finish transaction, applying any writes made in the transaction

db_txn_commit(&txn);

in the following examples, where a txn variable occurs, use of the appropriate transaction features is implied

create a type

db_field_t fields[4];
db_type_t* type;
// set field.type, field.name and field.name_len
db_field_set(fields[0], db_field_type_uint8f, "field-name-1", 12);
db_field_set(fields[1], db_field_type_int8f, "field-name-2", 12);
db_field_set(fields[2], db_field_type_string8, "field-name-3", 12);
db_field_set(fields[3], db_field_type_string8, "field-name-4", 12);
// arguments: db_env_t*, type_name, db_field_t*, field_count, flags, result
status_require(db_type_create(env, "test-type-name", fields, 4, 0, &type));

fields can be fixed length (for example for integers and floating point values) or variable length. all fixed size fields must come before variable size fields or an error is returned. possible field types are db_field_type_* macro variables, see api reference below. field type names ending with f are of fixed size, others are variable size. on variable size type names, the affixed number is the number of bits for storing the actual size. apart from indicating storage type and size, field types are a hint because no conversions take place

create records

// declarations
db_record_values_declare(values);
db_id_t id_1;
db_id_t id_2;
uint8_t value_1 = 11;
int8_t value_2 = -128;
uint8_t* value_3 = "abc";
uint8_t* value_4 = "abcde";
// memory allocation
status_require(db_record_values_new(type, &values));
// set field values.
// size argument is ignored for fixed length types.
// in this example the strings are stored without a trailing null character.
// arguments: db_record_values_t*, field_index, value_address, size.
db_record_values_set(&values, 0, &value_1, 0);
db_record_values_set(&values, 1, &value_2, 0);
db_record_values_set(&values, 2, value_3, 3);
db_record_values_set(&values, 3, value_4, 5);
// create one entry
status_require(db_record_create(txn, values, &id_1));
// create a second entry with a different value for the second field
value_2 = 123;
db_record_values_set(&values, 1, &value_2, 0);
status_require(db_record_create(txn, values, &id_2));
// memory deallocation
db_record_values_free(&values);

array data types

db_ids_t, db_records_t, db_relations_t store and pass collections of db_id_t, db_record_t and db_relation_t respectively and are special arrays with a fixed maximum size but variable length content. they make iteration easier to code and have a currently selected element. it is possible to get, remove or add elements without specifying an index, as the corresponding bindings will act on the current, last or next after the last element. memory for these arrays has to be allocated before use.

usage

status_declare;
// declare a new ids array variable
db_ids_declare(ids);
// allocate memory for three db_id_t elements
status_require(db_ids_new(3, &ids));
// add ids from left to right
db_ids_add(ids, 10);
db_ids_add(ids, 15);
db_ids_add(ids, 28);
// get the first element
db_ids_get(ids);
// the second element
db_ids_forward(ids);
db_ids_get(ids);
// reset current element to the first element
db_ids_rewind(ids);
// get element at specific index
db_ids_get_at(ids, 2);
db_ids_free(ids);

db_records_* and db_relations_* bindings work the same. see the api documentation for more features. you can always access the data in a plain c array of db_id_t, db_record_t or db_relation_t with the struct field data, for example ids.data

read records

if no results are found or the end of results has been reached, the returned status id is db_status_id_notfound. here is one example of how to handle this

// like status_require but tolerates notfound
status_require_read(db_record_read(selection, count, &results));
if(db_status_id_notfound != status.id) {
  field_data = db_record_ref(db_records_get_at(results, 0), 0);
}
// sets status.id to zero if the current status is db_status_id_notfound
db_status_success_if_notfound;

by unique identifier

db_ids_declare(ids);
db_records_declare(records);
db_record_value_t field_data;
db_record_t record;
status_require(db_records_new(3, &records));
status_require(db_ids_new(3, &ids));
db_ids_add(ids, 1);
db_ids_add(ids, 2);
db_ids_add(ids, 3);
status_require_read(db_record_get(txn, ids, 1, &records));
if(db_records_length(records)) {
  record = db_records_get_at(records, 0);
  // arguments: type, db_record_t, field_index
  field_data = db_record_ref(type, record, 1);
  // field_data: void* .data, size_t .size
}
db_ids_free(ids);
db_records_free(records);

all of type

// arguments: db_txn_t, db_type_t*, matcher, matcher_state, selection_address));
status_require(db_record_select(txn, type, 0, 0, &selection));
status_require_read(db_record_read(selection, 3, &records));
while(db_records_in_range(records)) {
  record = db_records_get(records);
  field_data = db_record_ref(type, record, 0);
  db_records_forward(records);
}

by custom matcher function and optionally either type or ids list

boolean record_matcher(db_type_t* type, db_record_t record, void* matcher_state) {
  db_record_value_t field_data;
  field_data = db_record_ref(type, record, 2);
  *((uint8_t*)(matcher_state)) = 1;
  return 1;
};
uint8_t matcher_state = 0;
status_require(db_record_select(txn, type, record_matcher, &matcher_state, &selection));

create relations

db_ids_declare(left);
db_ids_declare(right);
db_ids_declare(label);
status_require(db_ids_new(3, &left));
status_require(db_ids_new(2, &right));
status_require(db_ids_new(2, &label));
db_ids_add(left, 1);
db_ids_add(left, 2);
db_ids_add(left, 3);
db_ids_add(right, 4);
db_ids_add(right, 5);
db_ids_add(label, 6);
db_ids_add(label, 7);
// create relations between all given left and right records for each label. relations = left * right * label
status_require(db_relation_ensure(txn, left, right, label, 0, 0));
db_ids_free(left);
db_ids_free(right);
db_ids_free(label);

read relations

// declarations
db_ids_declare(ids_left);
db_ids_declare(ids_label);
db_relations_declare(relations);
db_relation_selection_declare(selection);
db_relation_t relation;
// memory allocation
status_require(db_ids_new(1, &ids_left));
status_require(db_ids_new(1, &ids_label));
status_require(db_relations_new(10, &relations));
// record ids to be used to filter
db_ids_add(ids_left, 1);
db_ids_add(ids_label, 6);
db_ids_add(ids_label, 7);
// select relations whose left side is in "ids_left" and label in "ids_label".
status_require(db_relation_select(txn, &ids_left, 0, &ids_label, 0, &selection));
// read 2 of the selected relations
status_require(db_relation_read(&selection, 2, &relations));
db_relation_selection_finish(&selection);
// display relations. "ordinal" might not be set unless a filter for left was used
while(db_relations_in_range(relations)) {
  relation = db_relations_get(relations);
  printf("relation: %lu %lu %lu %lu\n", relation.left, relation.label, relation.ordinal, relation.right);
  db_relations_forward(relations);
};
db_ids_free(ids_left);
db_ids_free(ids_label);
db_relations_free(relations);

create index

db_index_t* index;
// array of field indices to index
db_fields_len_t fields[2] = {1, 2};
status_require(db_index_create(env, type, fields, 2));
index = db_index_get(type, fields, 2);
  • existing records will be indexed on index creation
  • new records will be automatically added or removed from the index when they are created or deleted
  • there is a limit on the combined size of indexed field data for a record, which is defined by the lmdb compile-time constant MDB_MAXKEYSIZE, default 511 bytes minus db_id_t size. currently inserts with index data too large are rejected

read record ids from indices

db_index_selection_declare(selection);
db_ids_declare(ids);
db_record_values_declare(values);
uint8_t value_1 = 11;
uint8_t* value_2 = "abc";
// allocate memory
status_require(db_ids_new(2, &ids));
status_require(db_record_values_new(type, &values));
// set indexed values to search with. unused fields will be ignored
db_record_values_set(&values, 1, &value_1, 0);
db_record_values_set(&values, 2, &value_2, 3);
status_require_read(db_index_select(txn, *index, values, &selection));
if(db_status_id_notfound != status.id) {
  status_require_read(db_index_read(selection, 2, &ids));
}
db_status_success_if_notfound;
db_index_selection_finish(&selection);
db_ids_free(ids);
db_record_values_free(&values);

read records via indices

db_record_index_selection_declare(selection);
status_require_read(db_record_index_select(txn, *index, values, &selection));
if(db_status_id_notfound != status.id) {
  status_require_read(db_record_index_read(selection, 1, &records));
  record = db_records_get(records);
}

virtual records

virtual records carry the data in the identifier and only exist in relations or field data. one use-case are relations with a possibly large number of numeric values that dont need a separate data record, for example timestamps. they are to save space and processing costs. they can store data of any type that is equal to or smaller than id-size minus type-size. to create a virtual record type, use db_type_flag_virtual with db_type_create and only define a single field

db_id_t id;
uint32_t data;
db_field_t fields;
db_type_t* type;
// create virtual record type. must have only one field
db_field_set(fields, db_field_type_uint16, 0, 0);
status_require(db_type_create(env, "test-vtype", &fields, 1, db_type_flag_virtual, &type));
// create record. exists only as id
data = 123;
id = db_record_virtual_from_uint(type->id, data);
// get data. arguments: id, datatype
data = db_record_virtual_data_uint(id, uint32_t);

api

routines

db_close :: db_env_t*:env -> void
db_env_new :: db_env_t**:result -> status_t
db_field_type_size :: db_field_type_t:a -> uint8_t
db_ids_new :: size_t:length db_ids_t*:result_ids -> status_t
db_index_create :: db_env_t*:env db_type_t*:type db_fields_len_t*:fields db_fields_len_t:fields_len db_index_t**:result_index -> status_t
db_index_delete :: db_env_t*:env db_index_t*:index -> status_t
db_index_get :: db_type_t*:type db_fields_len_t*:fields db_fields_len_t:fields_len -> db_index_t*
db_index_read :: db_index_selection_t:selection db_count_t:count db_ids_t*:result_ids -> status_t
db_index_rebuild :: db_env_t*:env db_index_t*:index -> status_t
db_index_select :: db_txn_t:txn db_index_t:index db_record_values_t:values db_index_selection_t*:result -> status_t
db_index_selection_finish :: db_index_selection_t*:selection -> void
db_open :: uint8_t*:root db_open_options_t*:options db_env_t*:env -> status_t
db_open_options_set_defaults :: db_open_options_t*:a -> void
db_record_create :: db_txn_t:txn db_record_values_t:values db_id_t*:result -> status_t
db_record_data_to_values :: db_type_t*:type db_record_t:data db_record_values_t*:result -> status_t
db_record_delete :: db_txn_t:txn db_ids_t:ids -> status_t
db_record_delete_type :: db_txn_t:txn db_type_id_t:type_id -> status_t
db_record_get :: db_txn_t:txn db_ids_t:ids boolean:match_all db_records_t*:result_records -> status_t
db_record_index_read :: db_record_index_selection_t:selection db_count_t:count db_records_t*:result_records -> status_t
db_record_index_select :: db_txn_t:txn db_index_t:index db_record_values_t:values db_record_index_selection_t*:result -> status_t
db_record_index_selection_finish :: db_record_index_selection_t*:selection -> void
db_record_read :: db_record_selection_t:selection db_count_t:count db_records_t*:result_records -> status_t
db_record_ref :: db_type_t*:type db_record_t:record db_fields_len_t:field -> db_record_value_t
db_record_select :: db_txn_t:txn db_type_t*:type db_record_matcher_t:matcher void*:matcher_state db_record_selection_t*:result_selection -> status_t
db_record_selection_finish :: db_record_selection_t*:selection -> void
db_record_skip :: db_record_selection_t:selection db_count_t:count -> status_t
db_record_update :: db_txn_t:txn db_id_t:id db_record_values_t:values -> status_t
db_record_values_to_data :: db_record_values_t:values db_record_t*:result -> status_t
db_record_values_free :: db_record_values_t*:a -> void
db_record_values_new :: db_type_t*:type db_record_values_t*:result -> status_t
db_record_values_set :: db_record_values_t*:values db_fields_len_t:field_index void*:data size_t:size -> status_t
db_record_virtual :: db_type_id_t:type_id void*:data size_t:data_size -> db_id_t
db_record_virtual_data :: db_id_t:id void*:result size_t:result_size -> void*
db_records_to_ids :: db_records_t:records db_ids_t*:result_ids -> void
db_records_new :: size_t:length db_records_t*:result_records -> status_t
db_relation_delete :: db_txn_t:txn db_ids_t*:left db_ids_t*:right db_ids_t*:label db_ordinal_condition_t*:ordinal -> status_t
db_relation_ensure :: db_txn_t:txn db_ids_t:left db_ids_t:right db_ids_t:label db_relation_ordinal_generator_t:ordinal_generator void*:ordinal_generator_state -> status_t
db_relation_read :: db_relation_selection_t*:selection db_count_t:count db_relations_t*:result -> status_t
db_relation_select :: db_txn_t:txn db_ids_t*:left db_ids_t*:right db_ids_t*:label db_ordinal_condition_t*:ordinal db_relation_selection_t*:result -> status_t
db_relation_selection_finish :: db_relation_selection_t*:selection -> void
db_relation_skip :: db_relation_selection_t*:selection db_count_t:count -> status_t
db_relations_new :: size_t:length db_relations_t*:result_relations -> status_t
db_statistics :: db_txn_t:txn db_statistics_t*:result -> status_t
db_status_description :: status_t:a -> uint8_t*
db_status_name :: status_t:a -> uint8_t*
db_txn_abort :: db_txn_t*:a -> void
db_txn_begin :: db_txn_t*:a -> status_t
db_txn_begin_child :: db_txn_t:parent_txn db_txn_t*:a -> status_t
db_txn_commit :: db_txn_t*:a -> status_t
db_txn_write_begin :: db_txn_t*:a -> status_t
db_txn_write_begin_child :: db_txn_t:parent_txn db_txn_t*:a -> status_t
db_type_create :: db_env_t*:env uint8_t*:name db_field_t*:fields db_fields_len_t:fields_len uint8_t:flags db_type_t**:result -> status_t
db_type_delete :: db_env_t*:env db_type_id_t:id -> status_t
db_type_field_get :: db_type_t*:type uint8_t*:name -> db_field_t*
db_type_get :: db_env_t*:env uint8_t*:name -> db_type_t*

macros

boolean
db_batch_len
db_count_t
db_env_declare(name)
db_field_set(a, a_type, a_name)
db_field_type_binary128f
db_field_type_binary16
db_field_type_binary16f
db_field_type_binary256f
db_field_type_binary32
db_field_type_binary32f
db_field_type_binary64
db_field_type_binary64f
db_field_type_binary8
db_field_type_binary8f
db_field_type_float32f
db_field_type_float64f
db_field_type_int128f
db_field_type_int16f
db_field_type_int256f
db_field_type_int32f
db_field_type_int64f
db_field_type_int8f
db_field_type_string128f
db_field_type_string16
db_field_type_string16f
db_field_type_string256f
db_field_type_string32
db_field_type_string32f
db_field_type_string64
db_field_type_string64f
db_field_type_string8
db_field_type_string8f
db_field_type_t
db_field_type_uint128f
db_field_type_uint16f
db_field_type_uint256f
db_field_type_uint32f
db_field_type_uint64f
db_field_type_uint8f
db_fields_len_t
db_format_version
db_id_add_type(id, type_id)
db_id_element(id)
db_id_element_mask
db_id_mask
db_id_t
db_id_type(id)
db_id_type_mask
db_ids_add
db_ids_clear
db_ids_declare(name)
db_ids_forward
db_ids_free
db_ids_get
db_ids_get_at
db_ids_in_range
db_ids_length
db_ids_max_length
db_ids_remove
db_ids_rewind
db_ids_set_null
db_index_selection_declare(name)
db_index_selection_set_null(name)
db_indices_len_t
db_name_len_max
db_name_len_t
db_null
db_ordinal_t
db_record_index_selection_declare(name)
db_record_index_selection_set_null(name)
db_record_is_virtual(env, record_id)
db_record_selection_declare(name)
db_record_selection_set_null(name)
db_record_values_declare(name)
db_record_virtual_data_int
db_record_virtual_data_uint(id, type_name)
db_record_virtual_from_int
db_record_virtual_from_uint(type_id, data)
db_records_add
db_records_clear
db_records_declare(name)
db_records_forward
db_records_free
db_records_get
db_records_get_at
db_records_in_range
db_records_length
db_records_max_length
db_records_remove
db_records_rewind
db_records_set_null
db_relation_selection_declare(name)
db_relation_selection_set_null(name)
db_relations_add
db_relations_clear
db_relations_declare(name)
db_relations_forward
db_relations_free
db_relations_get
db_relations_get_at
db_relations_in_range
db_relations_length
db_relations_max_length
db_relations_remove
db_relations_rewind
db_relations_set_null
db_size_element_id
db_size_relation_data
db_size_relation_key
db_status_group_db
db_status_group_libc
db_status_group_lmdb
db_status_set_id_goto(status_id)
db_status_success_if_notfound
db_txn_abort_if_active(a)
db_txn_declare(env, name)
db_txn_is_active(a)
db_type_flag_virtual
db_type_get_by_id(env, type_id)
db_type_id_mask
db_type_id_t
db_type_is_virtual(type)
status_declare
status_declare_group(group)
status_goto
status_group_undefined
status_id_require(expression)
status_id_success
status_is_failure
status_is_success
status_require(expression)
status_require_read(expression)
status_reset
status_set_both(group_id, status_id)
status_set_both_goto(group_id, status_id)
status_set_group_goto(group_id)
status_set_id_goto(status_id)

types

db_field_type_size_t: uint8_t
status_id_t: int32_t
db_record_matcher_t: db_type_t* db_record_t void* -> boolean
db_relation_ordinal_generator_t: void* -> db_ordinal_t
db_relation_reader_t: db_relation_selection_t* db_count_t db_relations_t* -> status_t
db_env_t: struct
  dbi_records: MDB_dbi
  dbi_relation_ll: MDB_dbi
  dbi_relation_lr: MDB_dbi
  dbi_relation_rl: MDB_dbi
  dbi_system: MDB_dbi
  mdb_env: MDB_env*
  is_open: boolean
  root: uint8_t*
  mutex: pthread_mutex_t
  maxkeysize: uint32_t
  format: uint32_t
  types: db_type_t*
  types_len: db_type_id_t
db_field_t: struct
  name: uint8_t*
  type: db_field_type_t
  offset: db_fields_len_t
  size: db_field_type_size_t
db_index_selection_t: struct
  cursor: MDB_cursor*
db_index_t: struct db_index_t
  dbi: MDB_dbi
  fields: db_fields_len_t*
  fields_len: db_fields_len_t
  type: db_type_t*
db_open_options_t: struct
  is_read_only: boolean
  maximum_size: size_t
  maximum_reader_count: db_count_t
  maximum_db_count: db_count_t
  filesystem_has_ordered_writes: boolean
  env_open_flags: uint_least32_t
  file_permissions: uint16_t
db_ordinal_condition_t: struct
  min: db_ordinal_t
  max: db_ordinal_t
db_record_index_selection_t: struct
  index_selection: db_index_selection_t
  records_cursor: MDB_cursor*
db_record_selection_t: struct
  cursor: MDB_cursor*
  matcher: db_record_matcher_t
  matcher_state: void*
  options: uint8_t
  type: db_type_t*
db_record_t: struct
  id: db_id_t
  data: void*
  size: size_t
db_record_value_t: struct
  size: size_t
  data: void*
db_record_values_t: struct
  data: db_record_value_t*
  extent: db_fields_len_t
  type: db_type_t*
db_relation_selection_t: struct
  cursor: MDB_cursor* restrict
  cursor_2: MDB_cursor* restrict
  left: db_ids_t
  right: db_ids_t
  label: db_ids_t
  ids_set: void*
  ordinal: db_ordinal_condition_t
  options: uint8_t
  reader: void*
db_relation_t: struct
  left: db_id_t
  right: db_id_t
  label: db_id_t
  ordinal: db_ordinal_t
db_statistics_t: struct
  system: MDB_stat
  records: MDB_stat
  relation_lr: MDB_stat
  relation_rl: MDB_stat
  relation_ll: MDB_stat
db_txn_t: struct
  mdb_txn: MDB_txn*
  env: db_env_t*
db_type_t: struct
  fields_len: db_fields_len_t
  fields_fixed_count: db_fields_len_t
  fields_fixed_offsets: size_t*
  fields: db_field_t*
  flags: uint8_t
  id: db_type_id_t
  indices: struct db_index_t*
  indices_len: db_indices_len_t
  name: uint8_t*
  sequence: db_id_t
status_t: struct
  id: status_id_t
  group: uint8_t*

enum

db_status_id_success db_status_id_undefined db_status_id_condition_unfulfilled
  db_status_id_data_length db_status_id_different_format db_status_id_duplicate
  db_status_id_input_type db_status_id_invalid_argument db_status_id_max_element_id
  db_status_id_max_type_id db_status_id_max_type_id_size db_status_id_memory
  db_status_id_missing_argument_db_root db_status_id_notfound db_status_id_not_implemented
  db_status_id_path_not_accessible_db_root db_status_id_index_keysize db_status_id_type_field_order
  db_status_id_invalid_field_type db_status_id_last

other language bindings

compile-time configuration

these values can be set before compilation in c-precompiled/main/sph-db.h. once compiled, they can not be changed for a shared library. databases created with one configuration must only be used by code compiled with the same configuration. if necessary, for example, multiple shared libraries with different configuration can be created.

name default description
db_id_t uint64_t must be an unsigned integer. for record identifiers. will also contain the type id
db_type_id_t uint16_t must be an unsigned integer. for type ids. limits the number of possible types. currently can not be larger than 16 bit
db_ordinal_t uint16_t for relation order values.
db_id_mask UINT64_MAX maximum value for the id type (all digits set to one)
db_type_id_mask UINT16_MAX maximum value for the type-id type
db_name_len_t uint8_t to store name string lengths (for type and field names)
db_name_len_max UINT8_MAX maximum allowed name length
db_fields_len_t uint16_t for field indices. limits the number of possible fields
db_indices_len_t uint16_t limits the number of possible indices per type
db_count_t uint32_t for values like the count of elements to read. does not need to be larger than half size_t
db_batch_len 100 number of elements to process at once internally for example in db-record-select-delete. mostly for buffering db-id-t

additional features and caveats

  • make sure that you do not try to insert ordinals or ids bigger than what is defined to be possible by the data types for ordinals and record identifiers. otherwise numerical overflows might occur
  • ordinals are primarily intended to store relations in a pre-calculated order for fast ordered retrieval
  • to use db_relation_select and a filter by ordinal, "left" filter values must be given
  • readers can return results and indicate the end of results in the same call
  • the maximum number of types, or type creations more precisely, is currently 65535. this limit could be removed in the future

possible enhancements

  • having the compile-time configuration in the versioned sph-db.h isnt ideal
  • float values as ordinals has not been tested
  • currently index inserts with data too large are rejected with an error. maybe add an option to control what happens, for example to truncate instead
  • signal error when creating index for fields that are or might be too large
  • possibly more streamlined declaration/allocation/deallocation for api usage
  • validator functions for indices and relation data consistency
  • lift the type creation limit
  • remove ordinals entirely or allow zero size ordinals, as this feature adds at least 8 bit to every relation
  • nested transactions. supposedly possible in lmdb but not working
  • partial indices. with a data filter function given at index definition
  • at some places MDB_SET_RANGE and MDB_GET_BOTH_RANGE is used in succession. maybe get-both-range includes set-range and the latter can be left out
  • search with matcher functions in index keys
  • aliases for status bindings. status_require is a relatively long word to be written repeatedly

development

this section is for when you want to change sph-db itself. the primary source code is currently under source/sc. source/c-precompiled is updated by exe/compile-sc. code files from submodules are copied into source/sc/foreign before compilation from sc to c. depending on circumstances, in the future, the sc dependency could be dropped and the c code could be made primary. the general development stages for new sph-db features is design, basic code implementation, tests that use the new features, debugging, memory-leak tests (exe/valgrind-test) and documentation

setup

  • install the development dependencies listed above
  • clone the sourcecode repository "git clone https://github.com/sph-mn/sph-db.git"
  • clone the submodule repositories. "git submodule init" "git submodule update"

contribution

  • send feature requests, patches or pull requests via issues or e-mail and they will be considered
  • bug reports or design commentaries are welcome

further reading

see other/documentation for more details about internals and design considerations