zcash_client_sqlite: Use `prepare_cached` instead of manual statement caching.
`rusqlite` includes a mechanism for creating prepared statements that automatically caches them and reuses the caches when possible. This means that it's unnecessary for us to do our own caching, and also offers a minor performance improvement in that we don't need to eagerly prepare statements that we may not execute in the lifetime of a given `WalletDb` object. It also improves code locality, because the prepared statements are now adjacent in the code to the parameter assignment blocks that correspond to those statements. This also updates a number of `put_x` methods to use sqlite upsert functionality via the `ON CONFLICT` clause, instead of having to perform separate inserts and updates.
This commit is contained in:
parent
d2f105efe9
commit
8d86ffd9c4
|
@ -89,14 +89,6 @@ impl<'a> InsertAddress<'a> {
|
|||
/// [`WalletWrite`]: zcash_client_backend::data_api::WalletWrite
|
||||
pub struct DataConnStmtCache<'a, P> {
|
||||
pub(crate) wallet_db: &'a WalletDb<P>,
|
||||
stmt_insert_block: Statement<'a>,
|
||||
|
||||
stmt_insert_tx_meta: Statement<'a>,
|
||||
stmt_update_tx_meta: Statement<'a>,
|
||||
|
||||
stmt_insert_tx_data: Statement<'a>,
|
||||
stmt_update_tx_data: Statement<'a>,
|
||||
stmt_select_tx_ref: Statement<'a>,
|
||||
|
||||
stmt_mark_sapling_note_spent: Statement<'a>,
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
|
@ -129,32 +121,6 @@ impl<'a, P> DataConnStmtCache<'a, P> {
|
|||
Ok(
|
||||
DataConnStmtCache {
|
||||
wallet_db,
|
||||
stmt_insert_block: wallet_db.conn.prepare(
|
||||
"INSERT INTO blocks (height, hash, time, sapling_tree)
|
||||
VALUES (?, ?, ?, ?)",
|
||||
)?,
|
||||
stmt_insert_tx_meta: wallet_db.conn.prepare(
|
||||
"INSERT INTO transactions (txid, block, tx_index)
|
||||
VALUES (?, ?, ?)",
|
||||
)?,
|
||||
stmt_update_tx_meta: wallet_db.conn.prepare(
|
||||
"UPDATE transactions
|
||||
SET block = ?, tx_index = ? WHERE txid = ?",
|
||||
)?,
|
||||
stmt_insert_tx_data: wallet_db.conn.prepare(
|
||||
"INSERT INTO transactions (txid, created, expiry_height, raw, fee)
|
||||
VALUES (?, ?, ?, ?, ?)",
|
||||
)?,
|
||||
stmt_update_tx_data: wallet_db.conn.prepare(
|
||||
"UPDATE transactions
|
||||
SET expiry_height = :expiry_height,
|
||||
raw = :raw,
|
||||
fee = IFNULL(:fee, fee)
|
||||
WHERE txid = :txid",
|
||||
)?,
|
||||
stmt_select_tx_ref: wallet_db.conn.prepare(
|
||||
"SELECT id_tx FROM transactions WHERE txid = ?",
|
||||
)?,
|
||||
stmt_mark_sapling_note_spent: wallet_db.conn.prepare(
|
||||
"UPDATE sapling_received_notes SET spent = ? WHERE nf = ?"
|
||||
)?,
|
||||
|
@ -271,119 +237,6 @@ impl<'a, P> DataConnStmtCache<'a, P> {
|
|||
)
|
||||
}
|
||||
|
||||
/// Inserts information about a scanned block into the database.
|
||||
pub fn stmt_insert_block(
|
||||
&mut self,
|
||||
block_height: BlockHeight,
|
||||
block_hash: BlockHash,
|
||||
block_time: u32,
|
||||
commitment_tree: &sapling::CommitmentTree,
|
||||
) -> Result<(), SqliteClientError> {
|
||||
let mut encoded_tree = Vec::new();
|
||||
write_commitment_tree(commitment_tree, &mut encoded_tree).unwrap();
|
||||
|
||||
self.stmt_insert_block.execute(params![
|
||||
u32::from(block_height),
|
||||
&block_hash.0[..],
|
||||
block_time,
|
||||
encoded_tree
|
||||
])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Inserts the given transaction and its block metadata into the wallet.
|
||||
///
|
||||
/// Returns the database row for the newly-inserted transaction, or an error if the
|
||||
/// transaction exists.
|
||||
pub(crate) fn stmt_insert_tx_meta(
|
||||
&mut self,
|
||||
txid: &TxId,
|
||||
height: BlockHeight,
|
||||
tx_index: usize,
|
||||
) -> Result<i64, SqliteClientError> {
|
||||
self.stmt_insert_tx_meta.execute(params![
|
||||
&txid.as_ref()[..],
|
||||
u32::from(height),
|
||||
(tx_index as i64),
|
||||
])?;
|
||||
|
||||
Ok(self.wallet_db.conn.last_insert_rowid())
|
||||
}
|
||||
|
||||
/// Updates the block metadata for the given transaction.
|
||||
///
|
||||
/// Returns `false` if the transaction doesn't exist in the wallet.
|
||||
pub(crate) fn stmt_update_tx_meta(
|
||||
&mut self,
|
||||
height: BlockHeight,
|
||||
tx_index: usize,
|
||||
txid: &TxId,
|
||||
) -> Result<bool, SqliteClientError> {
|
||||
match self.stmt_update_tx_meta.execute(params![
|
||||
u32::from(height),
|
||||
(tx_index as i64),
|
||||
&txid.as_ref()[..],
|
||||
])? {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
_ => unreachable!("txid column is marked as UNIQUE"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts the given transaction and its data into the wallet.
|
||||
///
|
||||
/// Returns the database row for the newly-inserted transaction, or an error if the
|
||||
/// transaction exists.
|
||||
pub(crate) fn stmt_insert_tx_data(
|
||||
&mut self,
|
||||
txid: &TxId,
|
||||
created_at: Option<time::OffsetDateTime>,
|
||||
expiry_height: BlockHeight,
|
||||
raw_tx: &[u8],
|
||||
fee: Option<Amount>,
|
||||
) -> Result<i64, SqliteClientError> {
|
||||
self.stmt_insert_tx_data.execute(params![
|
||||
&txid.as_ref()[..],
|
||||
created_at,
|
||||
u32::from(expiry_height),
|
||||
raw_tx,
|
||||
fee.map(i64::from)
|
||||
])?;
|
||||
|
||||
Ok(self.wallet_db.conn.last_insert_rowid())
|
||||
}
|
||||
|
||||
/// Updates the data for the given transaction.
|
||||
///
|
||||
/// Returns `false` if the transaction doesn't exist in the wallet.
|
||||
pub(crate) fn stmt_update_tx_data(
|
||||
&mut self,
|
||||
expiry_height: BlockHeight,
|
||||
raw_tx: &[u8],
|
||||
fee: Option<Amount>,
|
||||
txid: &TxId,
|
||||
) -> Result<bool, SqliteClientError> {
|
||||
let sql_args: &[(&str, &dyn ToSql)] = &[
|
||||
(":expiry_height", &u32::from(expiry_height)),
|
||||
(":raw", &raw_tx),
|
||||
(":fee", &fee.map(i64::from)),
|
||||
(":txid", &&txid.as_ref()[..]),
|
||||
];
|
||||
match self.stmt_update_tx_data.execute(sql_args)? {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
_ => unreachable!("txid column is marked as UNIQUE"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the database row for the given `txid`, if the transaction is in the wallet.
|
||||
pub(crate) fn stmt_select_tx_ref(&mut self, txid: &TxId) -> Result<i64, SqliteClientError> {
|
||||
self.stmt_select_tx_ref
|
||||
.query_row([&txid.as_ref()[..]], |row| row.get(0))
|
||||
.map_err(SqliteClientError::from)
|
||||
}
|
||||
|
||||
/// Marks a given nullifier as having been revealed in the construction of the
|
||||
/// specified transaction.
|
||||
///
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
//! wallet.
|
||||
//! - `memo` the shielded memo associated with the output, if any.
|
||||
|
||||
use rusqlite::{named_params, OptionalExtension, ToSql};
|
||||
use rusqlite::{named_params, params, OptionalExtension, ToSql};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
|
@ -72,6 +72,7 @@ use zcash_primitives::{
|
|||
block::BlockHash,
|
||||
consensus::{self, BlockHeight, BranchId, NetworkUpgrade, Parameters},
|
||||
memo::{Memo, MemoBytes},
|
||||
merkle_tree::{write_commitment_tree, write_incremental_witness},
|
||||
sapling::CommitmentTree,
|
||||
transaction::{components::Amount, Transaction, TxId},
|
||||
zip32::{
|
||||
|
@ -94,7 +95,7 @@ use crate::{
|
|||
#[cfg(feature = "transparent-inputs")]
|
||||
use {
|
||||
crate::UtxoId,
|
||||
rusqlite::{params, Connection},
|
||||
rusqlite::Connection,
|
||||
std::collections::BTreeSet,
|
||||
zcash_client_backend::{
|
||||
address::AddressMetadata, encoding::AddressCodec, wallet::WalletTransparentOutput,
|
||||
|
@ -739,7 +740,22 @@ pub(crate) fn insert_block<'a, P>(
|
|||
block_time: u32,
|
||||
commitment_tree: &CommitmentTree,
|
||||
) -> Result<(), SqliteClientError> {
|
||||
stmts.stmt_insert_block(block_height, block_hash, block_time, commitment_tree)
|
||||
let mut encoded_tree = Vec::new();
|
||||
write_commitment_tree(commitment_tree, &mut encoded_tree).unwrap();
|
||||
|
||||
let mut stmt_insert_block = stmts.wallet_db.conn.prepare_cached(
|
||||
"INSERT INTO blocks (height, hash, time, sapling_tree)
|
||||
VALUES (?, ?, ?, ?)",
|
||||
)?;
|
||||
|
||||
stmt_insert_block.execute(params![
|
||||
u32::from(block_height),
|
||||
&block_hash.0[..],
|
||||
block_time,
|
||||
encoded_tree
|
||||
])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Inserts information about a mined transaction that was observed to
|
||||
|
@ -749,13 +765,25 @@ pub(crate) fn put_tx_meta<'a, P, N>(
|
|||
tx: &WalletTx<N>,
|
||||
height: BlockHeight,
|
||||
) -> Result<i64, SqliteClientError> {
|
||||
if !stmts.stmt_update_tx_meta(height, tx.index, &tx.txid)? {
|
||||
// It isn't there, so insert our transaction into the database.
|
||||
stmts.stmt_insert_tx_meta(&tx.txid, height, tx.index)
|
||||
} else {
|
||||
// It was there, so grab its row number.
|
||||
stmts.stmt_select_tx_ref(&tx.txid)
|
||||
}
|
||||
// It isn't there, so insert our transaction into the database.
|
||||
let mut stmt_upsert_tx_meta = stmts.wallet_db.conn.prepare_cached(
|
||||
"INSERT INTO transactions (txid, block, tx_index)
|
||||
VALUES (:txid, :block, :tx_index)
|
||||
ON CONFLICT (txid) DO UPDATE
|
||||
SET block = :block,
|
||||
tx_index = :tx_index
|
||||
RETURNING id_tx",
|
||||
)?;
|
||||
|
||||
let tx_params = named_params![
|
||||
":txid": &tx.txid.as_ref()[..],
|
||||
":block": u32::from(height),
|
||||
":tx_index": i64::try_from(tx.index).expect("transaction indices are representable as i64"),
|
||||
];
|
||||
|
||||
stmt_upsert_tx_meta
|
||||
.query_row(tx_params, |row| row.get::<_, i64>(0))
|
||||
.map_err(SqliteClientError::from)
|
||||
}
|
||||
|
||||
/// Inserts full transaction data into the database.
|
||||
|
@ -765,18 +793,31 @@ pub(crate) fn put_tx_data<'a, P>(
|
|||
fee: Option<Amount>,
|
||||
created_at: Option<time::OffsetDateTime>,
|
||||
) -> Result<i64, SqliteClientError> {
|
||||
let txid = tx.txid();
|
||||
let mut stmt_upsert_tx_data = stmts.wallet_db.conn.prepare_cached(
|
||||
"INSERT INTO transactions (txid, created, expiry_height, raw, fee)
|
||||
VALUES (:txid, :created_at, :expiry_height, :raw, :fee)
|
||||
ON CONFLICT (txid) DO UPDATE
|
||||
SET expiry_height = :expiry_height,
|
||||
raw = :raw,
|
||||
fee = IFNULL(:fee, fee)
|
||||
RETURNING id_tx",
|
||||
)?;
|
||||
|
||||
let txid = tx.txid();
|
||||
let mut raw_tx = vec![];
|
||||
tx.write(&mut raw_tx)?;
|
||||
|
||||
if !stmts.stmt_update_tx_data(tx.expiry_height(), &raw_tx, fee, &txid)? {
|
||||
// It isn't there, so insert our transaction into the database.
|
||||
stmts.stmt_insert_tx_data(&txid, created_at, tx.expiry_height(), &raw_tx, fee)
|
||||
} else {
|
||||
// It was there, so grab its row number.
|
||||
stmts.stmt_select_tx_ref(&txid)
|
||||
}
|
||||
let tx_params = named_params![
|
||||
":txid": &txid.as_ref()[..],
|
||||
":created_at": created_at,
|
||||
":expiry_height": u32::from(tx.expiry_height()),
|
||||
":raw": raw_tx,
|
||||
":fee": fee.map(i64::from),
|
||||
];
|
||||
|
||||
stmt_upsert_tx_data
|
||||
.query_row(tx_params, |row| row.get::<_, i64>(0))
|
||||
.map_err(SqliteClientError::from)
|
||||
}
|
||||
|
||||
/// Marks the given UTXO as having been spent.
|
||||
|
|
Loading…
Reference in New Issue