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.
Field | Size | Description |
---|---|---|
account | 64 bits | Service account |
table | 16 bits | Table 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 tablePrimary
: fetches primary key from an objectSecondary
: 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 ofT
, unlessT
is markeddefinitionWillNotChange()
. When you read old records, these values will bestd::nullopt
. - If
T
has a field with typeX
, then you may add newoptional<...>
fields to the end ofX
, unlessX
is markeddefinitionWillNotChange()
. This rule is recursive, including throughvector
,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:
Usage | Key | Value |
---|---|---|
primary index | prefix, 0, primary key | object data |
secondary index | prefix, i, secondary key | prefix, 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 tableK
: 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 withinkey
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