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

bitcoindevkit / bdk / 10529221059

23 Aug 2024 04:38PM UTC coverage: 82.011% (+0.2%) from 81.848%
10529221059

Pull #1569

github

web-flow
Merge 911085ae4 into 9e6ac72a6
Pull Request #1569: `bdk_core` WIP WIP WIP

495 of 556 new or added lines in 14 files covered. (89.03%)

5 existing lines in 4 files now uncovered.

11215 of 13675 relevant lines covered (82.01%)

13364.79 hits per line

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

71.1
/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<()> {
1,092✔
19
    let sql = format!("CREATE TABLE IF NOT EXISTS {}( name TEXT PRIMARY KEY NOT NULL, version INTEGER NOT NULL ) STRICT", SCHEMAS_TABLE_NAME);
1,092✔
20
    db_tx.execute(&sql, ())?;
1,092✔
21
    Ok(())
1,092✔
22
}
1,092✔
23

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

210
fn to_sql_error<E: std::error::Error + Send + Sync + 'static>(err: E) -> rusqlite::Error {
×
211
    rusqlite::Error::ToSqlConversionFailure(Box::new(err))
×
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
    pub fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
104✔
229
        let schema_v0: &[&str] = &[
104✔
230
            // full transactions
104✔
231
            &format!(
104✔
232
                "CREATE TABLE {} ( \
104✔
233
                txid TEXT PRIMARY KEY NOT NULL, \
104✔
234
                raw_tx BLOB, \
104✔
235
                last_seen INTEGER \
104✔
236
                ) STRICT",
104✔
237
                Self::TXS_TABLE_NAME,
104✔
238
            ),
104✔
239
            // floating txouts
104✔
240
            &format!(
104✔
241
                "CREATE TABLE {} ( \
104✔
242
                txid TEXT NOT NULL, \
104✔
243
                vout INTEGER NOT NULL, \
104✔
244
                value INTEGER NOT NULL, \
104✔
245
                script BLOB NOT NULL, \
104✔
246
                PRIMARY KEY (txid, vout) \
104✔
247
                ) STRICT",
104✔
248
                Self::TXOUTS_TABLE_NAME,
104✔
249
            ),
104✔
250
            // anchors
104✔
251
            &format!(
104✔
252
                "CREATE TABLE {} ( \
104✔
253
                txid TEXT NOT NULL REFERENCES {} (txid), \
104✔
254
                block_height INTEGER NOT NULL, \
104✔
255
                block_hash TEXT NOT NULL, \
104✔
256
                anchor BLOB NOT NULL, \
104✔
257
                PRIMARY KEY (txid, block_height, block_hash) \
104✔
258
                ) STRICT",
104✔
259
                Self::ANCHORS_TABLE_NAME,
104✔
260
                Self::TXS_TABLE_NAME,
104✔
261
            ),
104✔
262
        ];
104✔
263
        migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
104✔
264
    }
104✔
265

266
    /// Construct a [`TxGraph`] from an sqlite database.
267
    ///
268
    /// Remember to call [`Self::init_sqlite_tables`] beforehand.
269
    pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
104✔
270
        let mut changeset = Self::default();
104✔
271

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

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

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

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

334
    /// Persist `changeset` to the sqlite database.
335
    ///
336
    /// Remember to call [`Self::init_sqlite_tables`] beforehand.
337
    pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
56✔
338
        let mut statement = db_tx.prepare_cached(&format!(
56✔
339
            "INSERT INTO {}(txid, raw_tx) VALUES(:txid, :raw_tx) ON CONFLICT(txid) DO UPDATE SET raw_tx=:raw_tx",
56✔
340
            Self::TXS_TABLE_NAME,
56✔
341
        ))?;
56✔
342
        for tx in &self.txs {
56✔
343
            statement.execute(named_params! {
×
344
                ":txid": Impl(tx.compute_txid()),
×
345
                ":raw_tx": Impl(tx.as_ref().clone()),
×
346
            })?;
×
347
        }
348

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

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

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

388
        Ok(())
56✔
389
    }
56✔
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
    pub fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
273✔
400
        let schema_v0: &[&str] = &[
273✔
401
            // blocks
273✔
402
            &format!(
273✔
403
                "CREATE TABLE {} ( \
273✔
404
                block_height INTEGER PRIMARY KEY NOT NULL, \
273✔
405
                block_hash TEXT NOT NULL \
273✔
406
                ) STRICT",
273✔
407
                Self::BLOCKS_TABLE_NAME,
273✔
408
            ),
273✔
409
        ];
273✔
410
        migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
273✔
411
    }
273✔
412

413
    /// Construct a [`LocalChain`](local_chain::LocalChain) from sqlite database.
414
    ///
415
    /// Remember to call [`Self::init_sqlite_tables`] beforehand.
416
    pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
273✔
417
        let mut changeset = Self::default();
273✔
418

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

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

437
    /// Persist `changeset` to the sqlite database.
438
    ///
439
    /// Remember to call [`Self::init_sqlite_tables`] beforehand.
440
    pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
147✔
441
        let mut replace_statement = db_tx.prepare_cached(&format!(
147✔
442
            "REPLACE INTO {}(block_height, block_hash) VALUES(:block_height, :block_hash)",
147✔
443
            Self::BLOCKS_TABLE_NAME,
147✔
444
        ))?;
147✔
445
        let mut delete_statement = db_tx.prepare_cached(&format!(
147✔
446
            "DELETE FROM {} WHERE block_height=:block_height",
147✔
447
            Self::BLOCKS_TABLE_NAME,
147✔
448
        ))?;
147✔
449
        for (&height, &hash) in &self.blocks {
252✔
450
            match hash {
105✔
451
                Some(hash) => replace_statement.execute(named_params! {
105✔
452
                    ":block_height": height,
105✔
453
                    ":block_hash": Impl(hash),
105✔
454
                })?,
105✔
455
                None => delete_statement.execute(named_params! {
×
456
                    ":block_height": height,
×
457
                })?,
×
458
            };
459
        }
460

461
        Ok(())
147✔
462
    }
147✔
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
    pub fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
273✔
475
        let schema_v0: &[&str] = &[
273✔
476
            // last revealed
273✔
477
            &format!(
273✔
478
                "CREATE TABLE {} ( \
273✔
479
                descriptor_id TEXT PRIMARY KEY NOT NULL, \
273✔
480
                last_revealed INTEGER NOT NULL \
273✔
481
                ) STRICT",
273✔
482
                Self::LAST_REVEALED_TABLE_NAME,
273✔
483
            ),
273✔
484
        ];
273✔
485
        migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
273✔
486
    }
273✔
487

488
    /// Construct [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex) from sqlite database
489
    /// and given parameters.
490
    ///
491
    /// Remember to call [`Self::init_sqlite_tables`] beforehand.
492
    pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
273✔
493
        let mut changeset = Self::default();
273✔
494

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

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

513
    /// Persist `changeset` to the sqlite database.
514
    ///
515
    /// Remember to call [`Self::init_sqlite_tables`] beforehand.
516
    pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
147✔
517
        let mut statement = db_tx.prepare_cached(&format!(
147✔
518
            "REPLACE INTO {}(descriptor_id, last_revealed) VALUES(:descriptor_id, :last_revealed)",
147✔
519
            Self::LAST_REVEALED_TABLE_NAME,
147✔
520
        ))?;
147✔
521
        for (&descriptor_id, &last_revealed) in &self.last_revealed {
189✔
522
            statement.execute(named_params! {
42✔
523
                ":descriptor_id": Impl(descriptor_id),
42✔
524
                ":last_revealed": last_revealed,
42✔
525
            })?;
42✔
526
        }
527

528
        Ok(())
147✔
529
    }
147✔
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