Understanding Database Interactions

Now that our tables are defined, let's learn how to interact with those form a Zephyr program.

Accessing the Tables

Before starting to write to/read from the tables we defined in the zephyr.toml, we need to define them in our program.

#[derive(DatabaseDerive, Clone)]
#[with_name("feedback")]
pub struct Feedback {
    pub source: ScVal,
    pub hash: ScVal,
    pub text: ScVal,
    pub votes: ScVal,
}

We're defining here the table "feedback" of the one of the zephyr.toml as a struct and assigning it the DatabaseDerive macro (more on that later). When using DatabaseDerive we also need to specify the table's name. Moreover, the field names in the structure must be the same as defined in your zephyr.toml columns definition.

Now to access (in this case to write) the table from the program we just have to create an instance of the struct, which will represent a row in the table, and call the Environment functions to interact with the database, such as EnvClient::put(). More about database operations on the next page.

pub fn write_feedback(env: &EnvClient, feedback: Feedback) {
    env.put(&feedback)
}

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