• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bitcoindevkit / bdk / 10032626530

22 Jul 2024 12:36AM UTC coverage: 81.881% (-1.6%) from 83.434%
10032626530

Pull #1506

github

web-flow
Merge 515f252f2 into d99b3ef4b
Pull Request #1506: Standardize API ownership in `KeychainTxOutIndex`

893 of 1170 new or added lines in 14 files covered. (76.32%)

33 existing lines in 7 files now uncovered.

10950 of 13373 relevant lines covered (81.88%)

16589.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.59
/crates/chain/src/rusqlite_impl.rs
1
//! Module for stuff
2

3
use crate::*;
4
use core::str::FromStr;
5

6
use alloc::{borrow::ToOwned, boxed::Box, string::ToString, sync::Arc, vec::Vec};
7
use bitcoin::consensus::{Decodable, Encodable};
8
use rusqlite;
9
use rusqlite::named_params;
10
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
11
use rusqlite::OptionalExtension;
12
use rusqlite::Transaction;
13

14
/// Table name for schemas.
15
pub const SCHEMAS_TABLE_NAME: &str = "bdk_schemas";
16

17
/// Initialize the schema table.
18
fn init_schemas_table(db_tx: &Transaction) -> rusqlite::Result<()> {
792✔
19
    let sql = format!("CREATE TABLE IF NOT EXISTS {}( name TEXT PRIMARY KEY NOT NULL, version INTEGER NOT NULL ) STRICT", SCHEMAS_TABLE_NAME);
792✔
20
    db_tx.execute(&sql, ())?;
792✔
21
    Ok(())
792✔
22
}
792✔
23

24
/// Get schema version of `schema_name`.
25
fn schema_version(db_tx: &Transaction, schema_name: &str) -> rusqlite::Result<Option<u32>> {
792✔
26
    let sql = format!(
792✔
27
        "SELECT version FROM {} WHERE name=:name",
792✔
28
        SCHEMAS_TABLE_NAME
792✔
29
    );
792✔
30
    db_tx
792✔
31
        .query_row(&sql, named_params! { ":name": schema_name }, |row| {
792✔
32
            row.get::<_, u32>("version")
528✔
33
        })
792✔
34
        .optional()
792✔
35
}
792✔
36

37
/// Set the `schema_version` of `schema_name`.
38
fn set_schema_version(
264✔
39
    db_tx: &Transaction,
264✔
40
    schema_name: &str,
264✔
41
    schema_version: u32,
264✔
42
) -> rusqlite::Result<()> {
264✔
43
    let sql = format!(
264✔
44
        "REPLACE INTO {}(name, version) VALUES(:name, :version)",
264✔
45
        SCHEMAS_TABLE_NAME,
264✔
46
    );
264✔
47
    db_tx.execute(
264✔
48
        &sql,
264✔
49
        named_params! { ":name": schema_name, ":version": schema_version },
264✔
50
    )?;
264✔
51
    Ok(())
264✔
52
}
264✔
53

54
/// Runs logic that initializes/migrates the table schemas.
55
pub fn migrate_schema(
792✔
56
    db_tx: &Transaction,
792✔
57
    schema_name: &str,
792✔
58
    versioned_scripts: &[&[&str]],
792✔
59
) -> rusqlite::Result<()> {
792✔
60
    init_schemas_table(db_tx)?;
792✔
61
    let current_version = schema_version(db_tx, schema_name)?;
792✔
62
    let exec_from = current_version.map_or(0_usize, |v| v as usize + 1);
792✔
63
    let scripts_to_exec = versioned_scripts.iter().enumerate().skip(exec_from);
792✔
64
    for (version, &script) in scripts_to_exec {
1,056✔
65
        set_schema_version(db_tx, schema_name, version as u32)?;
264✔
66
        for statement in script {
660✔
67
            db_tx.execute(statement, ())?;
396✔
68
        }
69
    }
70
    Ok(())
792✔
71
}
792✔
72

73
impl FromSql for Impl<bitcoin::Txid> {
NEW
74
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
×
NEW
75
        bitcoin::Txid::from_str(value.as_str()?)
×
NEW
76
            .map(Self)
×
NEW
77
            .map_err(from_sql_error)
×
NEW
78
    }
×
79
}
80

81
impl ToSql for Impl<bitcoin::Txid> {
NEW
82
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
×
NEW
83
        Ok(self.to_string().into())
×
NEW
84
    }
×
85
}
86

87
impl FromSql for Impl<bitcoin::BlockHash> {
88
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
110✔
89
        bitcoin::BlockHash::from_str(value.as_str()?)
110✔
90
            .map(Self)
110✔
91
            .map_err(from_sql_error)
110✔
92
    }
110✔
93
}
94

95
impl ToSql for Impl<bitcoin::BlockHash> {
96
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
66✔
97
        Ok(self.to_string().into())
66✔
98
    }
66✔
99
}
100

101
#[cfg(feature = "miniscript")]
102
impl FromSql for Impl<DescriptorId> {
103
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
22✔
104
        DescriptorId::from_str(value.as_str()?)
22✔
105
            .map(Self)
22✔
106
            .map_err(from_sql_error)
22✔
107
    }
22✔
108
}
109

110
#[cfg(feature = "miniscript")]
111
impl ToSql for Impl<DescriptorId> {
112
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
22✔
113
        Ok(self.to_string().into())
22✔
114
    }
22✔
115
}
116

117
impl FromSql for Impl<bitcoin::Transaction> {
NEW
118
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
×
NEW
119
        bitcoin::Transaction::consensus_decode_from_finite_reader(&mut value.as_bytes()?)
×
NEW
120
            .map(Self)
×
NEW
121
            .map_err(from_sql_error)
×
NEW
122
    }
×
123
}
124

125
impl ToSql for Impl<bitcoin::Transaction> {
NEW
126
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
×
NEW
127
        let mut bytes = Vec::<u8>::new();
×
NEW
128
        self.consensus_encode(&mut bytes).map_err(to_sql_error)?;
×
NEW
129
        Ok(bytes.into())
×
NEW
130
    }
×
131
}
132

133
impl FromSql for Impl<bitcoin::ScriptBuf> {
NEW
134
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
×
NEW
135
        Ok(bitcoin::Script::from_bytes(value.as_bytes()?)
×
NEW
136
            .to_owned()
×
NEW
137
            .into())
×
NEW
138
    }
×
139
}
140

141
impl ToSql for Impl<bitcoin::ScriptBuf> {
NEW
142
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
×
NEW
143
        Ok(self.as_bytes().into())
×
NEW
144
    }
×
145
}
146

147
impl FromSql for Impl<bitcoin::Amount> {
NEW
148
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
×
NEW
149
        Ok(bitcoin::Amount::from_sat(value.as_i64()?.try_into().map_err(from_sql_error)?).into())
×
NEW
150
    }
×
151
}
152

153
impl ToSql for Impl<bitcoin::Amount> {
NEW
154
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
×
NEW
155
        let amount: i64 = self.to_sat().try_into().map_err(to_sql_error)?;
×
NEW
156
        Ok(amount.into())
×
NEW
157
    }
×
158
}
159

160
impl<A: Anchor + serde_crate::de::DeserializeOwned> FromSql for Impl<A> {
NEW
161
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
×
NEW
162
        serde_json::from_str(value.as_str()?)
×
NEW
163
            .map(Impl)
×
NEW
164
            .map_err(from_sql_error)
×
NEW
165
    }
×
166
}
167

168
impl<A: Anchor + serde_crate::Serialize> ToSql for Impl<A> {
NEW
169
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
×
NEW
170
        serde_json::to_string(&self.0)
×
NEW
171
            .map(Into::into)
×
NEW
172
            .map_err(to_sql_error)
×
NEW
173
    }
×
174
}
175

176
#[cfg(feature = "miniscript")]
177
impl FromSql for Impl<miniscript::Descriptor<miniscript::DescriptorPublicKey>> {
178
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
220✔
179
        miniscript::Descriptor::from_str(value.as_str()?)
220✔
180
            .map(Self)
220✔
181
            .map_err(from_sql_error)
220✔
182
    }
220✔
183
}
184

185
#[cfg(feature = "miniscript")]
186
impl ToSql for Impl<miniscript::Descriptor<miniscript::DescriptorPublicKey>> {
187
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
132✔
188
        Ok(self.to_string().into())
132✔
189
    }
132✔
190
}
191

192
impl FromSql for Impl<bitcoin::Network> {
193
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
110✔
194
        bitcoin::Network::from_str(value.as_str()?)
110✔
195
            .map(Self)
110✔
196
            .map_err(from_sql_error)
110✔
197
    }
110✔
198
}
199

200
impl ToSql for Impl<bitcoin::Network> {
201
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
66✔
202
        Ok(self.to_string().into())
66✔
203
    }
66✔
204
}
205

NEW
206
fn from_sql_error<E: std::error::Error + Send + Sync + 'static>(err: E) -> FromSqlError {
×
NEW
207
    FromSqlError::Other(Box::new(err))
×
NEW
208
}
×
209

NEW
210
fn to_sql_error<E: std::error::Error + Send + Sync + 'static>(err: E) -> rusqlite::Error {
×
NEW
211
    rusqlite::Error::ToSqlConversionFailure(Box::new(err))
×
NEW
212
}
×
213

214
impl<A> tx_graph::ChangeSet<A>
215
where
216
    A: Anchor + Clone + Ord + serde::Serialize + serde::de::DeserializeOwned,
217
{
218
    /// Schema name for [`tx_graph::ChangeSet`].
219
    pub const SCHEMA_NAME: &'static str = "bdk_txgraph";
220
    /// Name of table that stores full transactions and `last_seen` timestamps.
221
    pub const TXS_TABLE_NAME: &'static str = "bdk_txs";
222
    /// Name of table that stores floating txouts.
223
    pub const TXOUTS_TABLE_NAME: &'static str = "bdk_txouts";
224
    /// Name of table that stores [`Anchor`]s.
225
    pub const ANCHORS_TABLE_NAME: &'static str = "bdk_anchors";
226

227
    /// Initialize sqlite tables.
228
    fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
72✔
229
        let schema_v0: &[&str] = &[
72✔
230
            // full transactions
72✔
231
            &format!(
72✔
232
                "CREATE TABLE {} ( \
72✔
233
                txid TEXT PRIMARY KEY NOT NULL, \
72✔
234
                raw_tx BLOB, \
72✔
235
                last_seen INTEGER \
72✔
236
                ) STRICT",
72✔
237
                Self::TXS_TABLE_NAME,
72✔
238
            ),
72✔
239
            // floating txouts
72✔
240
            &format!(
72✔
241
                "CREATE TABLE {} ( \
72✔
242
                txid TEXT NOT NULL, \
72✔
243
                vout INTEGER NOT NULL, \
72✔
244
                value INTEGER NOT NULL, \
72✔
245
                script BLOB NOT NULL, \
72✔
246
                PRIMARY KEY (txid, vout) \
72✔
247
                ) STRICT",
72✔
248
                Self::TXOUTS_TABLE_NAME,
72✔
249
            ),
72✔
250
            // anchors
72✔
251
            &format!(
72✔
252
                "CREATE TABLE {} ( \
72✔
253
                txid TEXT NOT NULL REFERENCES {} (txid), \
72✔
254
                block_height INTEGER NOT NULL, \
72✔
255
                block_hash TEXT NOT NULL, \
72✔
256
                anchor BLOB NOT NULL, \
72✔
257
                PRIMARY KEY (txid, block_height, block_hash) \
72✔
258
                ) STRICT",
72✔
259
                Self::ANCHORS_TABLE_NAME,
72✔
260
                Self::TXS_TABLE_NAME,
72✔
261
            ),
72✔
262
        ];
72✔
263
        migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
72✔
264
    }
72✔
265

266
    /// Construct a [`TxGraph`] from an sqlite database.
267
    pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
40✔
268
        Self::init_sqlite_tables(db_tx)?;
40✔
269

270
        let mut changeset = Self::default();
40✔
271

272
        let mut statement = db_tx.prepare(&format!(
40✔
273
            "SELECT txid, raw_tx, last_seen FROM {}",
40✔
274
            Self::TXS_TABLE_NAME,
40✔
275
        ))?;
40✔
276
        let row_iter = statement.query_map([], |row| {
40✔
NEW
277
            Ok((
×
NEW
278
                row.get::<_, Impl<bitcoin::Txid>>("txid")?,
×
NEW
279
                row.get::<_, Option<Impl<bitcoin::Transaction>>>("raw_tx")?,
×
NEW
280
                row.get::<_, Option<u64>>("last_seen")?,
×
281
            ))
282
        })?;
40✔
283
        for row in row_iter {
40✔
NEW
284
            let (Impl(txid), tx, last_seen) = row?;
×
NEW
285
            if let Some(Impl(tx)) = tx {
×
NEW
286
                changeset.txs.insert(Arc::new(tx));
×
NEW
287
            }
×
NEW
288
            if let Some(last_seen) = last_seen {
×
NEW
289
                changeset.last_seen.insert(txid, last_seen);
×
NEW
290
            }
×
291
        }
292

293
        let mut statement = db_tx.prepare(&format!(
40✔
294
            "SELECT txid, vout, value, script FROM {}",
40✔
295
            Self::TXOUTS_TABLE_NAME,
40✔
296
        ))?;
40✔
297
        let row_iter = statement.query_map([], |row| {
40✔
NEW
298
            Ok((
×
NEW
299
                row.get::<_, Impl<bitcoin::Txid>>("txid")?,
×
NEW
300
                row.get::<_, u32>("vout")?,
×
NEW
301
                row.get::<_, Impl<bitcoin::Amount>>("value")?,
×
NEW
302
                row.get::<_, Impl<bitcoin::ScriptBuf>>("script")?,
×
303
            ))
304
        })?;
40✔
305
        for row in row_iter {
40✔
NEW
306
            let (Impl(txid), vout, Impl(value), Impl(script_pubkey)) = row?;
×
NEW
307
            changeset.txouts.insert(
×
NEW
308
                bitcoin::OutPoint { txid, vout },
×
NEW
309
                bitcoin::TxOut {
×
NEW
310
                    value,
×
NEW
311
                    script_pubkey,
×
NEW
312
                },
×
NEW
313
            );
×
314
        }
315

316
        let mut statement = db_tx.prepare(&format!(
40✔
317
            "SELECT json(anchor), txid FROM {}",
40✔
318
            Self::ANCHORS_TABLE_NAME,
40✔
319
        ))?;
40✔
320
        let row_iter = statement.query_map([], |row| {
40✔
NEW
321
            Ok((
×
NEW
322
                row.get::<_, Impl<A>>("json(anchor)")?,
×
NEW
323
                row.get::<_, Impl<bitcoin::Txid>>("txid")?,
×
324
            ))
325
        })?;
40✔
326
        for row in row_iter {
40✔
NEW
327
            let (Impl(anchor), Impl(txid)) = row?;
×
NEW
328
            changeset.anchors.insert((anchor, txid));
×
329
        }
330

331
        Ok(changeset)
40✔
332
    }
40✔
333

334
    /// Persist `changeset` to the sqlite database.
335
    pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
32✔
336
        Self::init_sqlite_tables(db_tx)?;
32✔
337

338
        let mut statement = db_tx.prepare_cached(&format!(
32✔
339
            "INSERT INTO {}(txid, raw_tx) VALUES(:txid, :raw_tx) ON CONFLICT(txid) DO UPDATE SET raw_tx=:raw_tx",
32✔
340
            Self::TXS_TABLE_NAME,
32✔
341
        ))?;
32✔
342
        for tx in &self.txs {
32✔
NEW
343
            statement.execute(named_params! {
×
NEW
344
                ":txid": Impl(tx.compute_txid()),
×
NEW
345
                ":raw_tx": Impl(tx.as_ref().clone()),
×
NEW
346
            })?;
×
347
        }
348

349
        let mut statement = db_tx
32✔
350
            .prepare_cached(&format!(
32✔
351
                "INSERT INTO {}(txid, last_seen) VALUES(:txid, :last_seen) ON CONFLICT(txid) DO UPDATE SET last_seen=:last_seen",
32✔
352
                Self::TXS_TABLE_NAME,
32✔
353
            ))?;
32✔
354
        for (&txid, &last_seen) in &self.last_seen {
32✔
NEW
355
            statement.execute(named_params! {
×
NEW
356
                ":txid": Impl(txid),
×
NEW
357
                ":last_seen": Some(last_seen),
×
NEW
358
            })?;
×
359
        }
360

361
        let mut statement = db_tx.prepare_cached(&format!(
32✔
362
            "REPLACE INTO {}(txid, vout, value, script) VALUES(:txid, :vout, :value, :script)",
32✔
363
            Self::TXOUTS_TABLE_NAME,
32✔
364
        ))?;
32✔
365
        for (op, txo) in &self.txouts {
32✔
NEW
366
            statement.execute(named_params! {
×
NEW
367
                ":txid": Impl(op.txid),
×
NEW
368
                ":vout": op.vout,
×
NEW
369
                ":value": Impl(txo.value),
×
NEW
370
                ":script": Impl(txo.script_pubkey.clone()),
×
NEW
371
            })?;
×
372
        }
373

374
        let mut statement = db_tx.prepare_cached(&format!(
32✔
375
            "REPLACE INTO {}(txid, block_height, block_hash, anchor) VALUES(:txid, :block_height, :block_hash, jsonb(:anchor))",
32✔
376
            Self::ANCHORS_TABLE_NAME,
32✔
377
        ))?;
32✔
378
        for (anchor, txid) in &self.anchors {
32✔
NEW
379
            let anchor_block = anchor.anchor_block();
×
NEW
380
            statement.execute(named_params! {
×
NEW
381
                ":txid": Impl(*txid),
×
NEW
382
                ":block_height": anchor_block.height,
×
NEW
383
                ":block_hash": Impl(anchor_block.hash),
×
NEW
384
                ":anchor": Impl(anchor.clone()),
×
NEW
385
            })?;
×
386
        }
387

388
        Ok(())
32✔
389
    }
32✔
390
}
391

392
impl local_chain::ChangeSet {
393
    /// Schema name for the changeset.
394
    pub const SCHEMA_NAME: &'static str = "bdk_localchain";
395
    /// Name of sqlite table that stores blocks of [`LocalChain`](local_chain::LocalChain).
396
    pub const BLOCKS_TABLE_NAME: &'static str = "bdk_blocks";
397

398
    /// Initialize sqlite tables for persisting [`local_chain::LocalChain`].
399
    fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
198✔
400
        let schema_v0: &[&str] = &[
198✔
401
            // blocks
198✔
402
            &format!(
198✔
403
                "CREATE TABLE {} ( \
198✔
404
                block_height INTEGER PRIMARY KEY NOT NULL, \
198✔
405
                block_hash TEXT NOT NULL \
198✔
406
                ) STRICT",
198✔
407
                Self::BLOCKS_TABLE_NAME,
198✔
408
            ),
198✔
409
        ];
198✔
410
        migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
198✔
411
    }
198✔
412

413
    /// Construct a [`LocalChain`](local_chain::LocalChain) from sqlite database.
414
    pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
110✔
415
        Self::init_sqlite_tables(db_tx)?;
110✔
416

417
        let mut changeset = Self::default();
110✔
418

419
        let mut statement = db_tx.prepare(&format!(
110✔
420
            "SELECT block_height, block_hash FROM {}",
110✔
421
            Self::BLOCKS_TABLE_NAME,
110✔
422
        ))?;
110✔
423
        let row_iter = statement.query_map([], |row| {
110✔
424
            Ok((
110✔
425
                row.get::<_, u32>("block_height")?,
110✔
426
                row.get::<_, Impl<bitcoin::BlockHash>>("block_hash")?,
110✔
427
            ))
428
        })?;
110✔
429
        for row in row_iter {
220✔
430
            let (height, Impl(hash)) = row?;
110✔
431
            changeset.blocks.insert(height, Some(hash));
110✔
432
        }
433

434
        Ok(changeset)
110✔
435
    }
110✔
436

437
    /// Persist `changeset` to the sqlite database.
438
    pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
88✔
439
        Self::init_sqlite_tables(db_tx)?;
88✔
440

441
        let mut replace_statement = db_tx.prepare_cached(&format!(
88✔
442
            "REPLACE INTO {}(block_height, block_hash) VALUES(:block_height, :block_hash)",
88✔
443
            Self::BLOCKS_TABLE_NAME,
88✔
444
        ))?;
88✔
445
        let mut delete_statement = db_tx.prepare_cached(&format!(
88✔
446
            "DELETE FROM {} WHERE block_height=:block_height",
88✔
447
            Self::BLOCKS_TABLE_NAME,
88✔
448
        ))?;
88✔
449
        for (&height, &hash) in &self.blocks {
154✔
450
            match hash {
66✔
451
                Some(hash) => replace_statement.execute(named_params! {
66✔
452
                    ":block_height": height,
66✔
453
                    ":block_hash": Impl(hash),
66✔
454
                })?,
66✔
NEW
455
                None => delete_statement.execute(named_params! {
×
NEW
456
                    ":block_height": height,
×
NEW
457
                })?,
×
458
            };
459
        }
460

461
        Ok(())
88✔
462
    }
88✔
463
}
464

465
#[cfg(feature = "miniscript")]
466
impl keychain_txout::ChangeSet {
467
    /// Schema name for the changeset.
468
    pub const SCHEMA_NAME: &'static str = "bdk_keychaintxout";
469
    /// Name for table that stores last revealed indices per descriptor id.
470
    pub const LAST_REVEALED_TABLE_NAME: &'static str = "bdk_descriptor_last_revealed";
471

472
    /// Initialize sqlite tables for persisting
473
    /// [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
474
    fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
198✔
475
        let schema_v0: &[&str] = &[
198✔
476
            // last revealed
198✔
477
            &format!(
198✔
478
                "CREATE TABLE {} ( \
198✔
479
                descriptor_id TEXT PRIMARY KEY NOT NULL, \
198✔
480
                last_revealed INTEGER NOT NULL \
198✔
481
                ) STRICT",
198✔
482
                Self::LAST_REVEALED_TABLE_NAME,
198✔
483
            ),
198✔
484
        ];
198✔
485
        migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
198✔
486
    }
198✔
487

488
    /// Construct [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex) from sqlite database
489
    /// and given parameters.
490
    pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
110✔
491
        Self::init_sqlite_tables(db_tx)?;
110✔
492

493
        let mut changeset = Self::default();
110✔
494

495
        let mut statement = db_tx.prepare(&format!(
110✔
496
            "SELECT descriptor_id, last_revealed FROM {}",
110✔
497
            Self::LAST_REVEALED_TABLE_NAME,
110✔
498
        ))?;
110✔
499
        let row_iter = statement.query_map([], |row| {
110✔
500
            Ok((
22✔
501
                row.get::<_, Impl<DescriptorId>>("descriptor_id")?,
22✔
502
                row.get::<_, u32>("last_revealed")?,
22✔
503
            ))
504
        })?;
110✔
505
        for row in row_iter {
132✔
506
            let (Impl(descriptor_id), last_revealed) = row?;
22✔
507
            changeset.last_revealed.insert(descriptor_id, last_revealed);
22✔
508
        }
509

510
        Ok(changeset)
110✔
511
    }
110✔
512

513
    /// Persist `changeset` to the sqlite database.
514
    pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
88✔
515
        Self::init_sqlite_tables(db_tx)?;
88✔
516

517
        let mut statement = db_tx.prepare_cached(&format!(
88✔
518
            "REPLACE INTO {}(descriptor_id, last_revealed) VALUES(:descriptor_id, :last_revealed)",
88✔
519
            Self::LAST_REVEALED_TABLE_NAME,
88✔
520
        ))?;
88✔
521
        for (&descriptor_id, &last_revealed) in &self.last_revealed {
110✔
522
            statement.execute(named_params! {
22✔
523
                ":descriptor_id": Impl(descriptor_id),
22✔
524
                ":last_revealed": last_revealed,
22✔
525
            })?;
22✔
526
        }
527

528
        Ok(())
88✔
529
    }
88✔
530
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc