Understanding Zephyr.toml And Database Interactions

Learn more about how your Zephyr program connects to the Mercury database.

The zephyr.toml is a crucial part of the newer versions of the Zephyr client-side tooling. The file is used to define the zephyr-related configurations of your rust crate. More specifically it defines the tables that your ingestion program will read from or write to during the execution. Besides the tables, you'll also have to define the columns for each of the tables you declare.

Choosing a Table's Structure and Its Naming

Zephyr tables are effectively translated to PostgreSQL tables under the hood (that is, on the host server). In light of this, we recommend following a layout that is SQL-efficient.

For what concerns naming, we are currently leveraging the ease of packing string-alike objects into integers through Sorban symbols, i.e. we are using symbols for table and column names. This means that table/column names can only be up to 9 characters, and that supported characters are a-zA-Z0-9_.

This means that table names will likely have to be abbreviated: "current_sequence" won't work while "curr_seq" will.

DatabaseDerive and Defining Tables Rust-Side

The magic of rust metaprogramming comes into play when working with the database. Assuming you've already read through the quickstart, let's jump to an immediate example. Before the DatabaseDerive macro, reading, and writing to the database was done as follows:

// This snippet is taken from xycLoans' zephyr program code.

env.db_write(
    "xlsupply",
    &["contract", "timestamp", "supply"],
    &[
        contract_id,
        &self.timestamp.to_be_bytes(),
        &supply.to_be_bytes(),
    ],
)
.unwrap()

Reading from the database and updating was even worse. This is unsound and non-idiomatic, but more importantly very unsafe. With the DatabaseDerive macro, this becomes much better and safer:

// Snipped from Script3's soroban governor indexer.

#[derive(DatabaseDerive, Clone)]
#[with_name("votes")]
pub struct Votes {
    pub contract: Hash,  // governor contract address
    // other fields ...
}

pub fn write_votes(env: &EnvClient, votes: Votes) {
    env.put(&votes)
}

The DatabaseDerive macro builds the required trait implementation to construct a code similar to the one used in the xycLoans example under the hood. For this to work, however, you need to make sure that the field names in the structure are the same as defined in your zephyr.toml columns definition.

Last updated