C++ Table

psibase::ServiceTables

template<typename ...Tables>
using psibase::ServiceTables = DbTables<DbId::service, Tables...>;

Defines tables in the service database.

psibase::WriteOnlyTables

template<typename ...Tables>
using psibase::WriteOnlyTables = DbTables<DbId::writeOnly, Tables...>;

Defines tables in the writeOnly database.

psibase::SubjectiveTables

template<typename ...Tables>
using psibase::SubjectiveTables = DbTables<DbId::subjective, Tables...>;

Defines tables in the subjective database.

psibase::DbTables

template<DbId Db, typename ...Tables>
struct psibase::DbTables {
    const DbId    db;      
    AccountNumber account; // the service runs on this account
    KvMode        mode;    

    DbTables(...); // Default constructor
    DbTables(...); // Constructor
    DbTables(...); // Constructor
    open(...);     // Open by table number
    open(...);     // Open by table type
    open(...);     // Open by record type
};

Defines the set of tables in a service.

Template arguments:

  • Db: the database holding the tables
  • Tables: one or more Table types; one for each table the service supports.

Modifying table set

You may add additional tables at the end.

Don't do any of the following; these will corrupt data:

  • Don't remove any tables
  • Don't reorder the tables

Prefix format

DbTables gives each table the following prefix. See Data format.

FieldSizeDescription
account64 bitsService account
table16 bitsTable number. First table is 0.

psibase::DbTables::DbTables

psibase::DbTables::DbTables();

Default constructor.

Assumes the desired service is running on the current action receiver account.

psibase::DbTables::DbTables

explicit psibase::DbTables::DbTables(
    AccountNumber account
);

Constructor.

account is the account the service runs on.

psibase::DbTables::DbTables

psibase::DbTables::DbTables(
    AccountNumber account,
    KvMode        mode
);

Constructor.

account is the account the service runs on.

psibase::DbTables::open

template<std::uint16_t Table>
auto psibase::DbTables::open() const;

Open by table number.

This gets a table by number. The first table is 0.

e.g. auto table = MyServiceTables{myServiceAccount}.open<2>();

Returns a Table.

psibase::DbTables::open

template<TableType T, typename I = boost::mp11::mp_find<boost::mp11::mp_list<Tables...>, T>>
auto psibase::DbTables::open() const;

Open by table type.

This gets a table by the table's type.

e.g. auto table = MyServiceTables{myServiceAccount}.open<MyTable>();

Returns a Table.

psibase::DbTables::open

template<typename RecordType, typename I = boost::mp11::mp_find<boost::mp11::mp_list<typename get_value_type<Tables>::type...>, RecordType>>
auto psibase::DbTables::open() const;

Open by record type.

This gets a table by the record type contained by the table.

e.g. auto table = MyServiceTables{myServiceAccount}.open<TableRecord>();

Returns a Table.

psibase::Table

template<typename T, auto Primary, auto ...Secondary>
struct psibase::Table {
    Table(...);    // Construct table with prefix
    Table(...);    // Construct table with prefix
    Table(...);    // Construct table with prefix
    Table(...);    // Construct table with prefix
    put(...);      // Store `value` into the table
    erase(...);    // Remove `key` from table
    remove(...);   // Remove object from table
    remove(...);   // Remove object from table
    remove(...);   // Remove object from table
    getIndex(...); // Get a primary or secondary index
    get(...);      // Look up table object by key using the first table index by default
    getView(...);  // Look up table object by key using the first table index by default
    first(...);    
    last(...);     
};

Stores objects in the key-value database.

Template arguments:

  • T: Type of object stored in table
  • Primary: fetches primary key from an object
  • Secondary: fetches a secondary key from an object. This is optional; there may be 0 or more secondary keys.

Primary and Secondary may be:

  • pointer-to-data-member. e.g. &MyType::key
  • a instance of a standard key type: NestedKey or CompositeKey
  • pointer-to-member-function which returns a key. e.g. &MyType::keyFunction. Be careful when using such functions, as changing the behavior of the function will corrupt the database, and such changes cannot be detected automatically.

Schema changes

You may modify the schema of an existing table the following ways:

  • Add new optional<...> fields to the end of T, unless T is marked definitionWillNotChange(). When you read old records, these values will be std::nullopt.
  • If T has a field with type X, then you may add new optional<...> fields to the end of X, unless X is marked definitionWillNotChange(). This rule is recursive, including through vector, optional, and user-defined types.
  • Add new secondary indexes after the existing ones. Old records will not appear in the new secondary indexes. Remove then re-add records to fill the new secondary indexes.

Don't do any of the following; these will corrupt data:

  • Don't modify any type marked definitionWillNotChange()
  • Don't remove definitionWillNotChange() from a type; this changes its serialization format
  • Don't add new fields to the middle of T or any types that it contains
  • Don't add new non-optional fields to T or any types that it contains
  • Don't reorder fields within T or any types that it contains
  • Don't remove fields from T or any types that it contains
  • Don't change the types of fields within T or any types that it contains
  • Don't reorder secondary indexes
  • Don't remove secondary indexes

Data format

The key-value pairs have this format:

UsageKeyValue
primary indexprefix, 0, primary keyobject data
secondary indexprefix, i, secondary keyprefix, 0, primary key

Each secondary index is numbered 1 <= i <= 255 above. The secondary indexes point to the primary index.

Table serializes keys using psio::to_key. It serializes T using fracpack.

psibase::Table::Table

psibase::Table::Table(
    DbId    db,
    KeyView prefix,
    KvMode  mode = KvMode::read
);

Construct table with prefix.

The prefix separates this table's data from other tables; see Data format.

This version of the constructor copies the data within prefix.

psibase::Table::Table

psibase::Table::Table(
    DbId                 db,
    std::vector<char> && prefix,
    KvMode               mode = KvMode::read
);

Construct table with prefix.

The prefix separates this table's data from other tables; see Data format.

psibase::Table::Table

psibase::Table::Table(
    KvHandle db,
    KeyView  prefix,
    KvMode   mode = KvMode::read
);

Construct table with prefix.

The prefix separates this table's data from other tables; see Data format.

This version of the constructor copies the data within prefix.

psibase::Table::Table

psibase::Table::Table(
    KvHandle             db,
    std::vector<char> && prefix,
    KvMode               mode = KvMode::read
);

Construct table with prefix.

The prefix separates this table's data from other tables; see Data format.

psibase::Table::put

template<typename U = T>
void psibase::Table::put(
    const U & value
);

Store value into the table.

If an object already exists with the same primary key, then the new object replaces it. If the object has any secondary keys which have the same value as another object, but not the one it's replacing, then put aborts the transaction.

psibase::Table::erase

template<compatible_key<key_type> Key>
void psibase::Table::erase(
    Key && key
);

Remove key from table.

This is equivalent to looking an object up by the key, then calling remove if found. The key must be the primary key.

psibase::Table::remove

void psibase::Table::remove(
    const T & oldValue
);

Remove object from table.

psibase::Table::remove

void psibase::Table::remove(
    psio::view<const T> oldValue
);

Remove object from table.

psibase::Table::remove

void psibase::Table::remove(
    psio::view<T> oldValue
);

Remove object from table.

psibase::Table::getIndex

template<int Idx>
auto psibase::Table::getIndex() const;

Get a primary or secondary index.

If Idx is 0, then this returns the primary index, else it returns a secondary index.

The result is TableIndex.

psibase::Table::get

template<compatible_key<primary_key_type> K = primary_key_type>
auto psibase::Table::get(
    K && key
) const;

Look up table object by key using the first table index by default.

psibase::Table::getView

template<compatible_key<primary_key_type> K = primary_key_type>
auto psibase::Table::getView(
    K && key
) const;

Look up table object by key using the first table index by default.

psibase::Table::first

std::optional<T> psibase::Table::first() const;

psibase::Table::last

std::optional<T> psibase::Table::last() const;

psibase::TableIndex

template<typename T, typename K>
struct psibase::TableIndex {
    TableIndex(...);  // Construct with prefix
    TableIndex(...);  // Construct with prefix
    begin(...);       // Get iterator to first object
    end(...);         // Get iterator past the end
    empty(...);       
    lower_bound(...); // Get iterator to first object with `key >= k`
    upper_bound(...); // Get iterator to first object with `key > k`
    subindex(...);    // Divide the key space
    subindex(...);    // Divide the key space, with an explicit key type for the new index.
    first(...);       
    last(...);        
    get(...);         // Look up object by key
    getView(...);     
};

A primary or secondary index in a Table.

Use Table::getIndex to get this.

Template arguments:

  • T: Type of object stored in table
  • K: Type of key this index uses

psibase::TableIndex::TableIndex

psibase::TableIndex::TableIndex(
    DbId                 db,
    std::vector<char> && prefix,
    bool                 is_secondary
);

Construct with prefix.

prefix identifies the range of database keys that the index occupies.

psibase::TableIndex::TableIndex

psibase::TableIndex::TableIndex(
    KvHandle             db,
    std::vector<char> && prefix,
    bool                 is_secondary
);

Construct with prefix.

prefix identifies the range of database keys that the index occupies.

psibase::TableIndex::begin

KvIterator<T> psibase::TableIndex::begin() const;

Get iterator to first object.

psibase::TableIndex::end

KvIterator<T> psibase::TableIndex::end() const;

Get iterator past the end.

psibase::TableIndex::empty

bool psibase::TableIndex::empty() const;

psibase::TableIndex::lower_bound

template<CompatibleKeyPrefix<K> K2>
KvIterator<T> psibase::TableIndex::lower_bound(
    K2 && k
) const;

Get iterator to first object with key >= k.

If the index's key is an std::tuple, then k may be the first n fields of the key.

Returns end if none found.

psibase::TableIndex::upper_bound

template<CompatibleKeyPrefix<K> K2>
KvIterator<T> psibase::TableIndex::upper_bound(
    K2 && k
) const;

Get iterator to first object with key > k.

If the index's key is an std::tuple, then k may be the first n fields of the key.

Returns end if none found.

psibase::TableIndex::subindex

template<typename Dummy = void, CompatibleKeyPrefix<K> K2>
TableIndex<T, KeySuffix<K2, K>> psibase::TableIndex::subindex(
    K2 && k
);

Divide the key space.

Assume K is a std::tuple<A, B, C, D>. If you call subindex with a tuple with the first n fields of K, e.g. std::tuple(aValue, bValue), then subindex returns another TableIndex which restricts its view to the subrange. e.g. it will iterate and search for std::tuple(cValue, dValue), holding aValue and bValue constant.

psibase::TableIndex::subindex

template<typename SubKey, typename K2>
TableIndex<T, SubKey> psibase::TableIndex::subindex(
    K2 && k
);

Divide the key space, with an explicit key type for the new index..

The combination of K2 and SubKey must be equivalent to K

psibase::TableIndex::first

std::optional<T> psibase::TableIndex::first() const;

psibase::TableIndex::last

std::optional<T> psibase::TableIndex::last() const;

psibase::TableIndex::get

template<compatible_key<K> K2 = K>
std::optional<T> psibase::TableIndex::get(
    K2 && k
) const;

Look up object by key.

If a matching key is found, then it returns a fresh object; it does not cache.

psibase::TableIndex::getView

template<compatible_key<K> K2 = K>
psio::shared_view_ptr<T> psibase::TableIndex::getView(
    K2 && k
) const;

psibase::KvIterator

template<typename T>
struct psibase::KvIterator {
    KvIterator(...);       // constructor
    operator++(...);       // preincrement (++it)
    operator++(...);       // postincrement (it++)
    operator--(...);       // predecrement (--it)
    operator--(...);       // postdecrement (it--)
    moveTo(...);           // Move iterator
    moveTo(...);           // Move iterator
    keyWithoutPrefix(...); // Get serialized key without prefix
    operator*(...);        // get object
    operator<=>(...);      // Comparisons
};

An iterator into a TableIndex.

Use TableIndex::begin, TableIndex::end, TableIndex::lower_bound, or TableIndex::upper_bound to get an iterator.

psibase::KvIterator::KvIterator

psibase::KvIterator::KvIterator(
    KvHandle             db,
    std::vector<char> && key,
    std::size_t          prefixSize,
    bool                 isSecondary,
    bool                 isEnd
);

constructor.

  • db identifies the database the table lives in.
  • See the "Key" column of Data format; the key field contains this.
  • prefixSize is the number of bytes within key which covers the index's prefix (includes the index number byte).
  • isSecondary is true if this iterator is for a secondary index.
  • isEnd is true if this iterator points past the end.

You don't need this constructor in most cases; use TableIndex::begin, TableIndex::end, TableIndex::lower_bound, or TableIndex::upper_bound instead.

psibase::KvIterator::operator++

KvIterator<T> & psibase::KvIterator::operator++();

preincrement (++it).

This moves the iterator forward.

The iterator has circular semantics. If you increment an end iterator, then it moves to the beginning of the index, or back to end again if empty.

psibase::KvIterator::operator++

KvIterator<T> psibase::KvIterator::operator++(
    int 
);

postincrement (it++).

This moves the iterator forward.

The iterator has circular semantics. If you increment an end iterator, then it moves to the beginning of the index, or back to end again if empty.

Note: postincrement (it++) and postdecrement (it--) have higher overhead than preincrement (++it) and predecrement (--it).

psibase::KvIterator::operator--

KvIterator<T> & psibase::KvIterator::operator--();

predecrement (--it).

This moves the iterator backward.

The iterator has circular semantics. If you decrement a begin iterator, then it moves to end.

psibase::KvIterator::operator--

KvIterator<T> psibase::KvIterator::operator--(
    int 
);

postdecrement (it--).

This moves the iterator backward.

The iterator has circular semantics. If you decrement a begin iterator, then it moves to end.

Note: postincrement (it++) and postdecrement (it--) have higher overhead than preincrement (++it) and predecrement (--it).

psibase::KvIterator::moveTo

void psibase::KvIterator::moveTo(
    int result
);

Move iterator.

This moves the iterator to the most-recent location found by raw::kvGreaterEqual, raw::kvLessThan, or raw::kvMax.

result is the return value of the raw call.

You don't need this function in most cases; use TableIndex::begin, TableIndex::end, TableIndex::lower_bound, TableIndex::upper_bound, or the iterator's increment or decrement operators instead.

psibase::KvIterator::moveTo

void psibase::KvIterator::moveTo(
    std::span<const char> k
);

Move iterator.

This moves the iterator to k. k does not include the prefix. May be used for GraphQL cursors; see keyWithoutPrefix.

psibase::KvIterator::keyWithoutPrefix

std::span<const char> psibase::KvIterator::keyWithoutPrefix() const;

Get serialized key without prefix.

The returned value can be passed to moveTo, e.g. for GraphQL cursors.

psibase::KvIterator::operator*

T psibase::KvIterator::operator*() const;

get object.

This reads an object from the database. It does not cache; it returns a fresh object each time it's used.

psibase::KvIterator::operator<=>

std::weak_ordering psibase::KvIterator::operator<=>(
    const KvIterator<T> & rhs
) const;

Comparisons.

psibase::KeyView

struct psibase::KeyView {
    std::span<const char> data; 
};

A serialized key (non-owning).

The serialized data has the same sort order as the non-serialized form