C++ Table

psibase::ServiceTables

template<typename ...Tables>
struct psibase::ServiceTables {
    AccountNumber account; // the service runs on this account

    ServiceTables(...); // Default constructor
    ServiceTables(...); // 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:

  • 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

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

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

psibase::ServiceTables::ServiceTables

psibase::ServiceTables::ServiceTables();

Default constructor.

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

psibase::ServiceTables::ServiceTables

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

Constructor.

account is the account the service runs on.

psibase::ServiceTables::open

template<std::uint16_t Table>
auto psibase::ServiceTables::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::ServiceTables::open

template<TableType T>
auto psibase::ServiceTables::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::ServiceTables::open

template<typename RecordType>
auto psibase::ServiceTables::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
    put(...);      // Store `value` into the table
    erase(...);    // Remove `key` from table
    remove(...);   // Remove object from table
    getIndex(...); // Get a primary or secondary index
    get(...);      
};

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
  • pointer-to-member-function which returns a key. e.g. &MyType::keyFunction
  • non-member function which takes a const T& as its only argument and returns a key
  • a callable object which takes a const T& as its only argument and returns a key

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
);

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
);

Construct table with prefix.

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

psibase::Table::put

void psibase::Table::put(
    const T & 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::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

auto psibase::Table::get(
    auto key
) const;

psibase::TableIndex

template<typename T, typename K>
struct psibase::TableIndex {
    TableIndex(...);  // Construct with prefix
    begin(...);       // Get iterator to first object
    end(...);         // Get iterator past the end
    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
    get(...);         // Look up object by key
};

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::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::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<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::get

template<compatible_key<K> K2>
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::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(
    DbId                 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