@@ -272,12 +272,25 @@ impl tx_graph::ChangeSet<ConfirmationBlockTime> {
272272 )
273273 }
274274
275+ /// Get v3 of sqlite [tx_graph::ChangeSet] schema
276+ pub fn schema_v3 ( ) -> String {
277+ format ! (
278+ "ALTER TABLE {} ADD COLUMN first_seen INTEGER" ,
279+ Self :: TXS_TABLE_NAME ,
280+ )
281+ }
282+
275283 /// Initialize sqlite tables.
276284 pub fn init_sqlite_tables ( db_tx : & rusqlite:: Transaction ) -> rusqlite:: Result < ( ) > {
277285 migrate_schema (
278286 db_tx,
279287 Self :: SCHEMA_NAME ,
280- & [ & Self :: schema_v0 ( ) , & Self :: schema_v1 ( ) , & Self :: schema_v2 ( ) ] ,
288+ & [
289+ & Self :: schema_v0 ( ) ,
290+ & Self :: schema_v1 ( ) ,
291+ & Self :: schema_v2 ( ) ,
292+ & Self :: schema_v3 ( ) ,
293+ ] ,
281294 )
282295 }
283296
@@ -288,22 +301,26 @@ impl tx_graph::ChangeSet<ConfirmationBlockTime> {
288301 let mut changeset = Self :: default ( ) ;
289302
290303 let mut statement = db_tx. prepare ( & format ! (
291- "SELECT txid, raw_tx, last_seen, last_evicted FROM {}" ,
304+ "SELECT txid, raw_tx, first_seen, last_seen, last_evicted FROM {}" ,
292305 Self :: TXS_TABLE_NAME ,
293306 ) ) ?;
294307 let row_iter = statement. query_map ( [ ] , |row| {
295308 Ok ( (
296309 row. get :: < _ , Impl < bitcoin:: Txid > > ( "txid" ) ?,
297310 row. get :: < _ , Option < Impl < bitcoin:: Transaction > > > ( "raw_tx" ) ?,
311+ row. get :: < _ , Option < u64 > > ( "first_seen" ) ?,
298312 row. get :: < _ , Option < u64 > > ( "last_seen" ) ?,
299313 row. get :: < _ , Option < u64 > > ( "last_evicted" ) ?,
300314 ) )
301315 } ) ?;
302316 for row in row_iter {
303- let ( Impl ( txid) , tx, last_seen, last_evicted) = row?;
317+ let ( Impl ( txid) , tx, first_seen , last_seen, last_evicted) = row?;
304318 if let Some ( Impl ( tx) ) = tx {
305319 changeset. txs . insert ( Arc :: new ( tx) ) ;
306320 }
321+ if let Some ( first_seen) = first_seen {
322+ changeset. first_seen . insert ( txid, first_seen) ;
323+ }
307324 if let Some ( last_seen) = last_seen {
308325 changeset. last_seen . insert ( txid, last_seen) ;
309326 }
@@ -376,6 +393,18 @@ impl tx_graph::ChangeSet<ConfirmationBlockTime> {
376393 } ) ?;
377394 }
378395
396+ let mut statement = db_tx. prepare_cached ( & format ! (
397+ "INSERT INTO {}(txid, first_seen) VALUES(:txid, :first_seen) ON CONFLICT(txid) DO UPDATE SET first_seen=:first_seen" ,
398+ Self :: TXS_TABLE_NAME ,
399+ ) ) ?;
400+ for ( & txid, & first_seen) in & self . first_seen {
401+ let checked_time = first_seen. to_sql ( ) ?;
402+ statement. execute ( named_params ! {
403+ ":txid" : Impl ( txid) ,
404+ ":first_seen" : Some ( checked_time) ,
405+ } ) ?;
406+ }
407+
379408 let mut statement = db_tx
380409 . prepare_cached ( & format ! (
381410 "INSERT INTO {}(txid, last_seen) VALUES(:txid, :last_seen) ON CONFLICT(txid) DO UPDATE SET last_seen=:last_seen" ,
@@ -653,7 +682,7 @@ mod test {
653682 }
654683
655684 #[ test]
656- fn v0_to_v2_schema_migration_is_backward_compatible ( ) -> anyhow:: Result < ( ) > {
685+ fn v0_to_v3_schema_migration_is_backward_compatible ( ) -> anyhow:: Result < ( ) > {
657686 type ChangeSet = tx_graph:: ChangeSet < ConfirmationBlockTime > ;
658687 let mut conn = rusqlite:: Connection :: open_in_memory ( ) ?;
659688
@@ -722,7 +751,7 @@ mod test {
722751 }
723752 }
724753
725- // Apply v1 & v2 sqlite schema to tables with data
754+ // Apply v1, v2, v3 sqlite schema to tables with data
726755 {
727756 let db_tx = conn. transaction ( ) ?;
728757 migrate_schema (
@@ -732,6 +761,7 @@ mod test {
732761 & ChangeSet :: schema_v0 ( ) ,
733762 & ChangeSet :: schema_v1 ( ) ,
734763 & ChangeSet :: schema_v2 ( ) ,
764+ & ChangeSet :: schema_v3 ( ) ,
735765 ] ,
736766 ) ?;
737767 db_tx. commit ( ) ?;
@@ -748,6 +778,45 @@ mod test {
748778 Ok ( ( ) )
749779 }
750780
781+ #[ test]
782+ fn can_persist_first_seen ( ) -> anyhow:: Result < ( ) > {
783+ use bitcoin:: hashes:: Hash ;
784+
785+ type ChangeSet = tx_graph:: ChangeSet < ConfirmationBlockTime > ;
786+ let mut conn = rusqlite:: Connection :: open_in_memory ( ) ?;
787+
788+ // Init tables
789+ {
790+ let db_tx = conn. transaction ( ) ?;
791+ ChangeSet :: init_sqlite_tables ( & db_tx) ?;
792+ db_tx. commit ( ) ?;
793+ }
794+
795+ let txid = bitcoin:: Txid :: all_zeros ( ) ;
796+ let first_seen = 100 ;
797+
798+ // Persist `first_seen`
799+ {
800+ let changeset = ChangeSet {
801+ first_seen : [ ( txid, first_seen) ] . into ( ) ,
802+ ..Default :: default ( )
803+ } ;
804+ let db_tx = conn. transaction ( ) ?;
805+ changeset. persist_to_sqlite ( & db_tx) ?;
806+ db_tx. commit ( ) ?;
807+ }
808+
809+ // Load from sqlite should succeed
810+ {
811+ let db_tx = conn. transaction ( ) ?;
812+ let changeset = ChangeSet :: from_sqlite ( & db_tx) ?;
813+ db_tx. commit ( ) ?;
814+ assert_eq ! ( changeset. first_seen. get( & txid) , Some ( & first_seen) ) ;
815+ }
816+
817+ Ok ( ( ) )
818+ }
819+
751820 #[ test]
752821 fn can_persist_last_evicted ( ) -> anyhow:: Result < ( ) > {
753822 use bitcoin:: hashes:: Hash ;
0 commit comments