tokio-postgres error handling - inserting type error

taeyang koh·2025년 2월 21일

Error occured during inserting data about Ethereum block data using tokio-postgres.

the original code was:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    dotenv().ok();
    let api_key = env::var("ALCHEMY_API_KEY")?;
    let db_url = env::var("DATABASE_URL")?;

    // Connect to PostgreSQL
    let (client, connection) = tokio_postgres::connect(&db_url, NoTls).await?;
    tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {}", e);
        }
    });

    // Fetch Ethereum block data
    let client_http = Client::new();
    let url = format!("https://eth-mainnet.alchemyapi.io/v2/{}", api_key);
    let payload = json!({
        "jsonrpc": "2.0",
        "method": "eth_getBlockByNumber",
        "params": ["latest", true],
        "id": 1
    });

    let response = client_http.post(&url).json(&payload).send().await?;
    let response_json: serde_json::Value = response.json().await?;

    if let Some(transactions) = response_json["result"]["transactions"].as_array() {
        for tx in transactions {
            let tx_data = Transaction {
                hash: tx["hash"].as_str().unwrap_or_default().to_string(),
                from: tx["from"].as_str().unwrap_or_default().to_string(),
                to: tx["to"].as_str().unwrap_or_default().to_string(),
                value: tx["value"].as_str().unwrap_or_default().to_string(),
                gasPrice: tx["gasPrice"].as_str().unwrap_or_default().to_string(),
            };

            // Save to PostgreSQL
            client.execute(
                "INSERT INTO transactions (tx_hash, from_address, to_address, value, gas_price)
                VALUES ($1, $2, $3, $4, $5)
                ON CONFLICT (tx_hash) DO NOTHING",
                &[
                    &tx_data.hash, 
                    &tx_data.from, 
                    &tx_data.to, 
                    &tx_data.value, 
                    &tx_data.gasPrice,
                ],
            ).await?;
        }
    }

    println!("Completed saving transaction!");
    Ok(())
}

but the table schema is

transactions (
    id SERIAL primary key,
    tx_hash text unique not null,
    from_address text not null,
    to_address text not null,
    value numeric not null,
    gas_price numeric not null,
    timestamp timestamp default now()
)

the data type mismatched(Error Code: E0277). So I tried to change type to i64, but same error occured.
the data type needed to be changed to Decimal to use Numerical type in PostgreSQL, so I tried below:

// ... existing code ...
use rust_decimal::Decimal;  // Add this import

struct Transaction {
    // ... existing code ...
    gas_price: Decimal,  // Changed from i64 to Decimal and fixed snake_case
    // ... existing code ...
}
// ... existing code ...
// ... existing code ...
[dependencies]
rust_decimal = { version = "1.32", features = ["postgres"] }
// ... existing code ...
// ... existing code ...
                value: tx["value"].as_str().unwrap_or_default().parse::<i64>().unwrap_or_default().into(),
                gas_price: tx["gasPrice"].as_str().unwrap_or_default().parse::<i64>().unwrap_or_default().into(),
// ... existing code ...

whole src code:

use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::env;
use dotenv::dotenv;
use tokio_postgres::{NoTls, Error};
use rust_decimal::Decimal;


#[derive(Debug, Serialize, Deserialize)]
struct Transaction {
    hash: String,
    from: String,
    to: String,
    value: Decimal,
    gas_price: Decimal,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    dotenv().ok();
    let api_key = env::var("ALCHEMY_API_KEY")?;
    let db_url = env::var("DATABASE_URL")?;

    // Connect to PostgreSQL
    let (client, connection) = tokio_postgres::connect(&db_url, NoTls).await?;
    tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {}", e);
        }
    });

    // Fetch Ethereum block data
    let client_http = Client::new();
    let url = format!("https://eth-mainnet.alchemyapi.io/v2/{}", api_key);
    let payload = json!({
        "jsonrpc": "2.0",
        "method": "eth_getBlockByNumber",
        "params": ["latest", true],
        "id": 1
    });

    let response = client_http.post(&url).json(&payload).send().await?;
    let response_json: serde_json::Value = response.json().await?;

    if let Some(transactions) = response_json["result"]["transactions"].as_array() {
        for tx in transactions {
            let tx_data = Transaction {
                hash: tx["hash"].as_str().unwrap_or_default().to_string(),
                from: tx["from"].as_str().unwrap_or_default().to_string(),
                to: tx["to"].as_str().unwrap_or_default().to_string(),
                value: tx["value"].as_str().unwrap_or_default().parse::<i64>().unwrap_or_default().into(),
                gas_price: tx["gasPrice"].as_str().unwrap_or_default().parse::<i64>().unwrap_or_default().into(),
            };

            // Save to PostgreSQL
            client.execute(
                "INSERT INTO transactions (tx_hash, from_address, to_address, value, gas_price)
                VALUES ($1, $2, $3, $4, $5)
                ON CONFLICT (tx_hash) DO NOTHING",
                &[
                    &tx_data.hash, 
                    &tx_data.from, 
                    &tx_data.to, 
                    &tx_data.value, 
                    &tx_data.gas_price,
                ],
            ).await?;
        }
    }

    println!("Completed saving transaction!");
    Ok(())
}

result from PostgreSQL
(used sql: SELECT * FROM transactions ORDER BY timestamp DESC LIMIT 5;)

profile
Passionate about crafting optimized systems that contribute to a brighter, better future

0개의 댓글