autofill/db/
addresses.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2* License, v. 2.0. If a copy of the MPL was not distributed with this
3* file, You can obtain one at http://mozilla.org/MPL/2.0/.
4*/
5
6use crate::db::{
7    models::{
8        address::{
9            AddressMeta, InternalAddress, UpdatableAddressFields, UpdatableAddressFieldsWithMeta,
10        },
11        Metadata,
12    },
13    schema::{ADDRESS_COMMON_COLS, ADDRESS_COMMON_VALS},
14};
15use crate::db::{timestamp_from_millis, with_savepoint, CounterUpdate};
16use crate::error::*;
17
18use rusqlite::{Connection, Transaction};
19use sync_guid::Guid;
20use types::Timestamp;
21
22pub(crate) fn add_address(
23    conn: &Connection,
24    new: UpdatableAddressFields,
25) -> Result<InternalAddress> {
26    let tx = conn.unchecked_transaction()?;
27    let now = Timestamp::now();
28
29    // We return an InternalAddress, so set it up first, including the missing
30    // fields, before we insert it.
31    let address = InternalAddress {
32        guid: Guid::random(),
33        name: new.name,
34        organization: new.organization,
35        street_address: new.street_address,
36        address_level3: new.address_level3,
37        address_level2: new.address_level2,
38        address_level1: new.address_level1,
39        postal_code: new.postal_code,
40        country: new.country,
41        tel: new.tel,
42        email: new.email,
43        metadata: Metadata {
44            time_created: now,
45            time_last_modified: now,
46            ..Default::default()
47        },
48    };
49    add_internal_address(&tx, &address)?;
50    tx.commit()?;
51    Ok(address)
52}
53
54/// Adds an address **including metadata**, taking the guid, timestamps and sync
55/// change counter from the caller rather than generating them. Normally you will
56/// use `add_address` instead; this is for importing records from another store
57/// that already have metadata.
58pub(crate) fn add_address_with_meta(
59    conn: &Connection,
60    fields: UpdatableAddressFields,
61    meta: AddressMeta,
62) -> Result<InternalAddress> {
63    let tx = conn.unchecked_transaction()?;
64    let address = internal_address_from_meta(fields, &meta);
65    add_internal_address(&tx, &address)?;
66    tx.commit()?;
67    Ok(address)
68}
69
70/// Adds multiple addresses **including metadata** within a single transaction.
71/// Each record gets its own result, so a record that fails to insert is reported
72/// as `Err(message)` without aborting the rest of the batch.
73pub(crate) fn add_many_addresses_with_meta(
74    conn: &Connection,
75    entries: Vec<UpdatableAddressFieldsWithMeta>,
76) -> Result<Vec<std::result::Result<InternalAddress, String>>> {
77    let tx = conn.unchecked_transaction()?;
78    let mut results = Vec::with_capacity(entries.len());
79    for entry in entries {
80        let address = internal_address_from_meta(entry.fields, &entry.meta);
81        match with_savepoint(&tx, || add_internal_address(&tx, &address))? {
82            Ok(()) => results.push(Ok(address)),
83            Err(e) => results.push(Err(e.to_string())),
84        }
85    }
86    tx.commit()?;
87    Ok(results)
88}
89
90/// Removes every address and every address tombstone, in one transaction.
91///
92/// Deleting the rows alone is not enough. A delete leaves a tombstone behind for
93/// any guid the sync mirror knows, and the insert trigger then rejects re-adding
94/// that guid, so a wipe that kept them could not be followed by a re-import of
95/// the same records. Clearing both tables is what makes the wipe repeatable.
96pub(crate) fn delete_all_addresses(conn: &Connection) -> Result<()> {
97    let tx = conn.unchecked_transaction()?;
98    tx.execute("DELETE FROM addresses_data", [])?;
99    // After the data, so the tombstones the delete trigger just created go too.
100    tx.execute("DELETE FROM addresses_tombstones", [])?;
101    tx.commit()?;
102    Ok(())
103}
104
105/// Adds tombstones for records that were deleted locally but not yet uploaded,
106/// within a single transaction and with a result per record. `time_deleted` comes
107/// from the caller rather than being stamped as now, so that a deletion imported
108/// from another store keeps its original time. Without the tombstone the next
109/// sync has nothing to say the record was deleted and takes the server copy.
110pub(crate) fn add_many_address_tombstones(
111    conn: &Connection,
112    tombstones: Vec<(String, i64)>,
113) -> Result<Vec<std::result::Result<String, String>>> {
114    let tx = conn.unchecked_transaction()?;
115    let mut results = Vec::with_capacity(tombstones.len());
116    for (guid, time_deleted) in tombstones {
117        let inserted = with_savepoint(&tx, || {
118            tx.execute(
119                "INSERT INTO addresses_tombstones (guid, time_deleted)
120                 VALUES (:guid, :time_deleted)",
121                rusqlite::named_params! {
122                    ":guid": &guid,
123                    ":time_deleted": timestamp_from_millis(time_deleted),
124                },
125            )?;
126            Ok(())
127        })?;
128        match inserted {
129            Ok(()) => results.push(Ok(guid)),
130            Err(e) => results.push(Err(e.to_string())),
131        }
132    }
133    tx.commit()?;
134    Ok(results)
135}
136
137fn internal_address_from_meta(
138    fields: UpdatableAddressFields,
139    meta: &AddressMeta,
140) -> InternalAddress {
141    InternalAddress {
142        guid: Guid::new(&meta.guid),
143        name: fields.name,
144        organization: fields.organization,
145        street_address: fields.street_address,
146        address_level3: fields.address_level3,
147        address_level2: fields.address_level2,
148        address_level1: fields.address_level1,
149        postal_code: fields.postal_code,
150        country: fields.country,
151        tel: fields.tel,
152        email: fields.email,
153        metadata: Metadata {
154            time_created: timestamp_from_millis(meta.time_created),
155            time_last_used: timestamp_from_millis(meta.time_last_used.unwrap_or(0)),
156            time_last_modified: timestamp_from_millis(meta.time_last_modified),
157            times_used: meta.times_used,
158            sync_change_counter: meta.sync_change_counter,
159        },
160    }
161}
162
163/// Updates an address **including metadata**, setting both its fields and its
164/// timestamps and `times_used` to the supplied values. Normally you will use
165/// `update_address` instead, which owns the metadata itself; this is for keeping
166/// a record identical to one held in another store. Errors with `NoSuchRecord`
167/// if the guid is absent.
168pub(crate) fn update_address_with_meta(
169    conn: &Connection,
170    fields: UpdatableAddressFields,
171    meta: AddressMeta,
172) -> Result<()> {
173    let tx = conn.unchecked_transaction()?;
174
175    let address = internal_address_from_meta(fields, &meta);
176    // Checked up front because `update_internal_address` asserts on the number
177    // of rows changed rather than returning an error.
178    let exists: bool = tx.query_row(
179        "SELECT EXISTS(SELECT 1 FROM addresses_data WHERE guid = :guid)",
180        rusqlite::named_params! { ":guid": address.guid },
181        |row| row.get(0),
182    )?;
183    if !exists {
184        return Err(Error::NoSuchRecord(address.guid.to_string()));
185    }
186    update_internal_address(
187        &tx,
188        &address,
189        CounterUpdate::Set(address.metadata.sync_change_counter),
190    )?;
191    tx.commit()?;
192    Ok(())
193}
194
195pub(crate) fn add_internal_address(tx: &Transaction<'_>, address: &InternalAddress) -> Result<()> {
196    tx.execute(
197        &format!(
198            "INSERT INTO addresses_data (
199                {common_cols},
200                sync_change_counter
201            ) VALUES (
202                {common_vals},
203                :sync_change_counter
204            )",
205            common_cols = ADDRESS_COMMON_COLS,
206            common_vals = ADDRESS_COMMON_VALS,
207        ),
208        rusqlite::named_params! {
209            ":guid": address.guid,
210            ":name": address.name,
211            ":organization": address.organization,
212            ":street_address": address.street_address,
213            ":address_level3": address.address_level3,
214            ":address_level2": address.address_level2,
215            ":address_level1": address.address_level1,
216            ":postal_code": address.postal_code,
217            ":country": address.country,
218            ":tel": address.tel,
219            ":email": address.email,
220            ":time_created": address.metadata.time_created,
221            ":time_last_used": address.metadata.time_last_used,
222            ":time_last_modified": address.metadata.time_last_modified,
223            ":times_used": address.metadata.times_used,
224            ":sync_change_counter": address.metadata.sync_change_counter,
225        },
226    )?;
227    Ok(())
228}
229
230pub(crate) fn get_address(conn: &Connection, guid: &Guid) -> Result<InternalAddress> {
231    let sql = format!(
232        "SELECT
233            {common_cols},
234            sync_change_counter
235        FROM addresses_data
236        WHERE guid = :guid",
237        common_cols = ADDRESS_COMMON_COLS
238    );
239    conn.query_row(&sql, [guid], InternalAddress::from_row)
240        .map_err(|e| match e {
241            rusqlite::Error::QueryReturnedNoRows => Error::NoSuchRecord(guid.to_string()),
242            e => e.into(),
243        })
244}
245
246pub(crate) fn get_all_addresses(conn: &Connection) -> Result<Vec<InternalAddress>> {
247    let sql = format!(
248        "SELECT
249            {common_cols},
250            sync_change_counter
251        FROM addresses_data",
252        common_cols = ADDRESS_COMMON_COLS
253    );
254
255    let mut stmt = conn.prepare(&sql)?;
256    let addresses = stmt
257        .query_map([], InternalAddress::from_row)?
258        .collect::<std::result::Result<Vec<InternalAddress>, _>>()?;
259    Ok(addresses)
260}
261
262pub(crate) fn count_all_addresses(conn: &Connection) -> Result<i64> {
263    let sql = "SELECT COUNT(*)
264        FROM addresses_data";
265
266    let mut stmt = conn.prepare(sql)?;
267    let count: i64 = stmt.query_row([], |row| row.get(0))?;
268    Ok(count)
269}
270
271/// Updates just the "updatable" columns - suitable for exposure as a public
272/// API.
273pub(crate) fn update_address(
274    conn: &Connection,
275    guid: &Guid,
276    address: &UpdatableAddressFields,
277) -> Result<()> {
278    let tx = conn.unchecked_transaction()?;
279    tx.execute(
280        "UPDATE addresses_data
281        SET name                = :name,
282            organization        = :organization,
283            street_address      = :street_address,
284            address_level3      = :address_level3,
285            address_level2      = :address_level2,
286            address_level1      = :address_level1,
287            postal_code         = :postal_code,
288            country             = :country,
289            tel                 = :tel,
290            email               = :email,
291            time_last_modified  = :time_last_modified,
292            sync_change_counter = sync_change_counter + 1
293        WHERE guid              = :guid",
294        rusqlite::named_params! {
295            ":name": address.name,
296            ":organization": address.organization,
297            ":street_address": address.street_address,
298            ":address_level3": address.address_level3,
299            ":address_level2": address.address_level2,
300            ":address_level1": address.address_level1,
301            ":postal_code": address.postal_code,
302            ":country": address.country,
303            ":tel": address.tel,
304            ":email": address.email,
305            ":time_last_modified": Timestamp::now(),
306            ":guid": guid,
307        },
308    )?;
309
310    tx.commit()?;
311    Ok(())
312}
313
314/// Updates all fields including metadata - although the change counter gets
315/// slightly special treatment, see `CounterUpdate`.
316pub(crate) fn update_internal_address(
317    tx: &Transaction<'_>,
318    address: &InternalAddress,
319    counter: CounterUpdate,
320) -> Result<()> {
321    let (counter_sql, counter_value) = counter.as_sql();
322    let rows_changed = tx.execute(
323        &format!(
324            "UPDATE addresses_data SET
325            name                = :name,
326            organization        = :organization,
327            street_address      = :street_address,
328            address_level3      = :address_level3,
329            address_level2      = :address_level2,
330            address_level1      = :address_level1,
331            postal_code         = :postal_code,
332            country             = :country,
333            tel                 = :tel,
334            email               = :email,
335            time_created        = :time_created,
336            time_last_used      = :time_last_used,
337            time_last_modified  = :time_last_modified,
338            times_used          = :times_used,
339            sync_change_counter = {counter_sql}
340        WHERE guid              = :guid"
341        ),
342        rusqlite::named_params! {
343            ":name": address.name,
344            ":organization": address.organization,
345            ":street_address": address.street_address,
346            ":address_level3": address.address_level3,
347            ":address_level2": address.address_level2,
348            ":address_level1": address.address_level1,
349            ":postal_code": address.postal_code,
350            ":country": address.country,
351            ":tel": address.tel,
352            ":email": address.email,
353            ":time_created": address.metadata.time_created,
354            ":time_last_used": address.metadata.time_last_used,
355            ":time_last_modified": address.metadata.time_last_modified,
356            ":times_used": address.metadata.times_used,
357            ":counter": counter_value,
358            ":guid": address.guid,
359        },
360    )?;
361    // Something went badly wrong if we are asking to update a row that doesn't
362    // exist, or somehow we updated more than 1!
363    assert_eq!(rows_changed, 1);
364    Ok(())
365}
366
367pub(crate) fn delete_address(conn: &Connection, guid: &Guid) -> Result<bool> {
368    let tx = conn.unchecked_transaction()?;
369
370    // execute returns how many rows were affected.
371    let exists = tx.execute(
372        "DELETE FROM addresses_data
373            WHERE guid = :guid",
374        rusqlite::named_params! {
375            ":guid": guid,
376        },
377    )? != 0;
378    tx.commit()?;
379    Ok(exists)
380}
381
382pub fn touch(conn: &Connection, guid: &Guid) -> Result<()> {
383    let tx = conn.unchecked_transaction()?;
384    let now_ms = Timestamp::now();
385
386    tx.execute(
387        "UPDATE addresses_data
388        SET time_last_used              = :time_last_used,
389            times_used                  = times_used + 1,
390            sync_change_counter         = sync_change_counter + 1
391        WHERE guid                      = :guid",
392        rusqlite::named_params! {
393            ":time_last_used": now_ms,
394            ":guid": guid,
395        },
396    )?;
397
398    tx.commit()?;
399    Ok(())
400}
401
402#[cfg(test)]
403mod tests {
404    use super::*;
405    use crate::db::{schema::create_empty_sync_temp_tables, test::new_mem_db};
406    use sync_guid::Guid;
407    use types::Timestamp;
408
409    #[allow(dead_code)]
410    fn get_all(
411        conn: &Connection,
412        table_name: String,
413    ) -> rusqlite::Result<Vec<String>, rusqlite::Error> {
414        let mut stmt = conn.prepare(&format!(
415            "SELECT guid FROM {table_name}",
416            table_name = table_name
417        ))?;
418        let rows = stmt.query_map([], |row| row.get(0))?;
419
420        let mut guids = Vec::new();
421        for guid_result in rows {
422            guids.push(guid_result?);
423        }
424
425        Ok(guids)
426    }
427
428    fn insert_tombstone_record(
429        conn: &Connection,
430        guid: String,
431    ) -> rusqlite::Result<usize, rusqlite::Error> {
432        conn.execute(
433            "INSERT INTO addresses_tombstones (
434                guid,
435                time_deleted
436            ) VALUES (
437                :guid,
438                :time_deleted
439            )",
440            rusqlite::named_params! {
441                ":guid": guid,
442                ":time_deleted": Timestamp::now(),
443            },
444        )
445    }
446
447    #[test]
448    fn test_address_create_and_read() {
449        let db = new_mem_db();
450
451        let saved_address = add_address(
452            &db,
453            UpdatableAddressFields {
454                name: "jane doe".to_string(),
455                street_address: "123 Main Street".to_string(),
456                address_level2: "Seattle, WA".to_string(),
457                country: "United States".to_string(),
458
459                ..UpdatableAddressFields::default()
460            },
461        )
462        .expect("should contain saved address");
463
464        // check that the add function populated the guid field
465        assert_ne!(Guid::default(), saved_address.guid);
466
467        // check that the time created and time last modified were set
468        assert_ne!(0, saved_address.metadata.time_created.as_millis());
469        assert_ne!(0, saved_address.metadata.time_last_modified.as_millis());
470
471        assert_eq!(0, saved_address.metadata.sync_change_counter);
472
473        // get created address
474        let retrieved_address = get_address(&db, &saved_address.guid)
475            .expect("should contain optional retrieved address");
476        assert_eq!(saved_address.guid, retrieved_address.guid);
477        assert_eq!(saved_address.name, retrieved_address.name);
478        assert_eq!(
479            saved_address.street_address,
480            retrieved_address.street_address
481        );
482        assert_eq!(
483            saved_address.address_level2,
484            retrieved_address.address_level2
485        );
486        assert_eq!(saved_address.country, retrieved_address.country);
487
488        // converting the created record into a tombstone to check that it's not returned on a second `get_address` call
489        let delete_result = delete_address(&db, &saved_address.guid);
490        assert!(delete_result.is_ok());
491        assert!(delete_result.unwrap());
492
493        assert!(get_address(&db, &saved_address.guid).is_err());
494    }
495
496    #[test]
497    fn test_address_missing_guid() {
498        let db = new_mem_db();
499        let guid = Guid::random();
500        let result = get_address(&db, &guid);
501
502        assert_eq!(
503            result.unwrap_err().to_string(),
504            Error::NoSuchRecord(guid.to_string()).to_string()
505        );
506    }
507
508    #[test]
509    fn test_address_read_all() {
510        let db = new_mem_db();
511
512        let saved_address = add_address(
513            &db,
514            UpdatableAddressFields {
515                name: "jane doe".to_string(),
516                street_address: "123 Second Avenue".to_string(),
517                address_level2: "Chicago, IL".to_string(),
518                country: "United States".to_string(),
519
520                ..UpdatableAddressFields::default()
521            },
522        )
523        .expect("should contain saved address");
524
525        let saved_address2 = add_address(
526            &db,
527            UpdatableAddressFields {
528                name: "john deer".to_string(),
529                street_address: "123 First Avenue".to_string(),
530                address_level2: "Los Angeles, CA".to_string(),
531                country: "United States".to_string(),
532
533                ..UpdatableAddressFields::default()
534            },
535        )
536        .expect("should contain saved address");
537
538        // creating a third address with a tombstone to ensure it's not returned
539        let saved_address3 = add_address(
540            &db,
541            UpdatableAddressFields {
542                name: "abraham lincoln".to_string(),
543                street_address: "1600 Pennsylvania Ave NW".to_string(),
544                address_level2: "Washington, DC".to_string(),
545                country: "United States".to_string(),
546
547                ..UpdatableAddressFields::default()
548            },
549        )
550        .expect("should contain saved address");
551
552        let delete_result = delete_address(&db, &saved_address3.guid);
553        assert!(delete_result.is_ok());
554        assert!(delete_result.unwrap());
555
556        let retrieved_addresses =
557            get_all_addresses(&db).expect("Should contain all saved addresses");
558
559        assert!(!retrieved_addresses.is_empty());
560        let expected_number_of_addresses = 2;
561        assert_eq!(expected_number_of_addresses, retrieved_addresses.len());
562
563        let address_count = count_all_addresses(&db).expect("Should count all saved addresses");
564        assert_eq!(expected_number_of_addresses, address_count as usize);
565
566        let retrieved_address_guids = [
567            retrieved_addresses[0].guid.as_str(),
568            retrieved_addresses[1].guid.as_str(),
569        ];
570        assert!(retrieved_address_guids.contains(&saved_address.guid.as_str()));
571        assert!(retrieved_address_guids.contains(&saved_address2.guid.as_str()));
572    }
573
574    #[test]
575    fn test_address_update() {
576        let db = new_mem_db();
577
578        let saved_address = add_address(
579            &db,
580            UpdatableAddressFields {
581                name: "john doe".to_string(),
582                street_address: "1300 Broadway".to_string(),
583                address_level2: "New York, NY".to_string(),
584                country: "United States".to_string(),
585
586                ..UpdatableAddressFields::default()
587            },
588        )
589        .expect("should contain saved address");
590        // change_counter starts at 0
591        assert_eq!(0, saved_address.metadata.sync_change_counter);
592
593        let expected_name = "john paul deer".to_string();
594        let update_result = update_address(
595            &db,
596            &saved_address.guid,
597            &UpdatableAddressFields {
598                name: expected_name.clone(),
599                organization: "".to_string(),
600                street_address: "123 First Avenue".to_string(),
601                address_level3: "".to_string(),
602                address_level2: "Denver, CO".to_string(),
603                address_level1: "".to_string(),
604                postal_code: "".to_string(),
605                country: "United States".to_string(),
606                tel: "".to_string(),
607                email: "".to_string(),
608            },
609        );
610        assert!(update_result.is_ok());
611
612        let updated_address =
613            get_address(&db, &saved_address.guid).expect("should contain optional updated address");
614
615        assert_eq!(saved_address.guid, updated_address.guid);
616        assert_eq!(expected_name, updated_address.name);
617
618        //check that the sync_change_counter was incremented
619        assert_eq!(1, updated_address.metadata.sync_change_counter);
620    }
621
622    #[test]
623    fn test_address_update_refreshes_time_last_modified() -> Result<()> {
624        let db = new_mem_db();
625
626        // Backdated, so the update has something to move it away from: two
627        // calls in the same millisecond would tell us nothing.
628        add_address_with_meta(&db, test_fields("123 Main Street"), test_meta("abc", 0))?;
629        assert_eq!(
630            get_address(&db, &Guid::new("abc"))?
631                .metadata
632                .time_last_modified
633                .as_millis(),
634            3000
635        );
636
637        update_address(&db, &Guid::new("abc"), &test_fields("456 Second Avenue"))?;
638
639        // Consumers reconcile on this field -- latest wins -- so an update that
640        // leaves it alone makes the record look older than it is.
641        assert!(
642            get_address(&db, &Guid::new("abc"))?
643                .metadata
644                .time_last_modified
645                .as_millis()
646                > 3000
647        );
648
649        Ok(())
650    }
651
652    #[test]
653    fn test_address_update_internal_address() -> Result<()> {
654        let mut db = new_mem_db();
655        let tx = db.transaction()?;
656
657        let guid = Guid::random();
658        add_internal_address(
659            &tx,
660            &InternalAddress {
661                guid: guid.clone(),
662                name: "john paul deer".to_string(),
663                organization: "".to_string(),
664                street_address: "123 First Avenue".to_string(),
665                address_level3: "".to_string(),
666                address_level2: "Denver, CO".to_string(),
667                address_level1: "".to_string(),
668                postal_code: "".to_string(),
669                country: "United States".to_string(),
670                tel: "".to_string(),
671                email: "".to_string(),
672                ..Default::default()
673            },
674        )?;
675
676        let expected_name = "john paul dear";
677        update_internal_address(
678            &tx,
679            &InternalAddress {
680                guid: guid.clone(),
681                name: expected_name.to_string(),
682                organization: "".to_string(),
683                street_address: "123 First Avenue".to_string(),
684                address_level3: "".to_string(),
685                address_level2: "Denver, CO".to_string(),
686                address_level1: "".to_string(),
687                postal_code: "".to_string(),
688                country: "United States".to_string(),
689                tel: "".to_string(),
690                email: "".to_string(),
691                ..Default::default()
692            },
693            CounterUpdate::Leave,
694        )?;
695
696        let record_exists: bool = tx.query_row(
697            "SELECT EXISTS (
698                SELECT 1
699                FROM addresses_data
700                WHERE guid = :guid
701                AND name = :name
702                AND sync_change_counter = 0
703            )",
704            [&guid.to_string(), &expected_name.to_string()],
705            |row| row.get(0),
706        )?;
707        assert!(record_exists);
708
709        Ok(())
710    }
711
712    #[test]
713    fn test_address_delete() {
714        fn num_tombstones(conn: &Connection) -> u32 {
715            let stmt = "SELECT COUNT(*) from addresses_tombstones";
716            conn.query_row(stmt, [], |row| Ok(row.get::<_, u32>(0).unwrap()))
717                .unwrap()
718        }
719
720        let db = new_mem_db();
721        create_empty_sync_temp_tables(&db).expect("should create temp tables");
722
723        let saved_address = add_address(
724            &db,
725            UpdatableAddressFields {
726                name: "jane doe".to_string(),
727                street_address: "123 Second Avenue".to_string(),
728                address_level2: "Chicago, IL".to_string(),
729                country: "United States".to_string(),
730                ..UpdatableAddressFields::default()
731            },
732        )
733        .expect("first create should work");
734
735        delete_address(&db, &saved_address.guid).expect("delete should work");
736        // should be no tombstone as it wasn't in the mirror.
737        assert_eq!(num_tombstones(&db), 0);
738
739        // do it again, but with it in the mirror.
740        let saved_address = add_address(
741            &db,
742            UpdatableAddressFields {
743                name: "jane doe".to_string(),
744                street_address: "123 Second Avenue".to_string(),
745                address_level2: "Chicago, IL".to_string(),
746                country: "United States".to_string(),
747                ..UpdatableAddressFields::default()
748            },
749        )
750        .expect("create 2nd address should work");
751        db.execute(
752            &format!(
753                "INSERT INTO addresses_mirror (guid, payload) VALUES ('{}', 'whatever')",
754                saved_address.guid,
755            ),
756            [],
757        )
758        .expect("manual insert into mirror");
759        delete_address(&db, &saved_address.guid).expect("2nd delete");
760        assert_eq!(num_tombstones(&db), 1);
761    }
762
763    #[test]
764    fn test_address_trigger_on_create() {
765        let db = new_mem_db();
766        let tx = db.unchecked_transaction().expect("should get a tx");
767        let guid = Guid::random();
768
769        // create a tombstone record
770        let tombstone_result = insert_tombstone_record(&db, guid.to_string());
771        assert!(tombstone_result.is_ok());
772
773        // create a new address with the tombstone's guid
774        let address = InternalAddress {
775            guid,
776            name: "jane doe".to_string(),
777            street_address: "123 Second Avenue".to_string(),
778            address_level2: "Chicago, IL".to_string(),
779            country: "United States".to_string(),
780            ..Default::default()
781        };
782
783        let add_address_result = add_internal_address(&tx, &address);
784        assert!(add_address_result.is_err());
785
786        let expected_error_message = "guid exists in `addresses_tombstones`";
787        assert!(add_address_result
788            .unwrap_err()
789            .to_string()
790            .contains(expected_error_message))
791    }
792
793    #[test]
794    fn test_address_trigger_on_delete() {
795        let db = new_mem_db();
796        let tx = db.unchecked_transaction().expect("should get a tx");
797        let guid = Guid::random();
798
799        // create an address
800        let address = InternalAddress {
801            guid,
802            name: "jane doe".to_string(),
803            street_address: "123 Second Avenue".to_string(),
804            address_level2: "Chicago, IL".to_string(),
805            country: "United States".to_string(),
806            ..Default::default()
807        };
808
809        let add_address_result = add_internal_address(&tx, &address);
810        assert!(add_address_result.is_ok());
811
812        // create a tombstone record with the same guid
813        let tombstone_result = insert_tombstone_record(&db, address.guid.to_string());
814        assert!(tombstone_result.is_err());
815
816        let expected_error_message = "guid exists in `addresses_data`";
817        assert_eq!(
818            expected_error_message,
819            tombstone_result.unwrap_err().to_string()
820        );
821    }
822
823    #[test]
824    fn test_address_touch() -> Result<()> {
825        let db = new_mem_db();
826        let saved_address = add_address(
827            &db,
828            UpdatableAddressFields {
829                name: "jane doe".to_string(),
830                street_address: "123 Second Avenue".to_string(),
831                address_level2: "Chicago, IL".to_string(),
832                country: "United States".to_string(),
833
834                ..UpdatableAddressFields::default()
835            },
836        )?;
837
838        assert_eq!(saved_address.metadata.sync_change_counter, 0);
839        assert_eq!(saved_address.metadata.times_used, 0);
840
841        touch(&db, &saved_address.guid)?;
842
843        let touched_address = get_address(&db, &saved_address.guid)?;
844
845        assert_eq!(touched_address.metadata.sync_change_counter, 1);
846        assert_eq!(touched_address.metadata.times_used, 1);
847
848        Ok(())
849    }
850
851    fn test_fields(street_address: &str) -> UpdatableAddressFields {
852        UpdatableAddressFields {
853            name: "jane doe".to_string(),
854            street_address: street_address.to_string(),
855            address_level2: "Seattle, WA".to_string(),
856            country: "United States".to_string(),
857            ..UpdatableAddressFields::default()
858        }
859    }
860
861    fn test_meta(guid: &str, sync_change_counter: i64) -> AddressMeta {
862        AddressMeta {
863            guid: guid.to_string(),
864            time_created: 1000,
865            time_last_used: Some(2000),
866            time_last_modified: 3000,
867            times_used: 4,
868            sync_change_counter,
869        }
870    }
871
872    #[test]
873    fn test_address_add_with_meta() -> Result<()> {
874        let db = new_mem_db();
875
876        let saved =
877            add_address_with_meta(&db, test_fields("123 Main Street"), test_meta("abc", 2))?;
878
879        // the supplied guid is used rather than a fresh one being generated.
880        assert_eq!(saved.guid.as_str(), "abc");
881
882        let retrieved = get_address(&db, &Guid::new("abc"))?;
883        assert_eq!(retrieved.street_address, "123 Main Street");
884        assert_eq!(retrieved.metadata.time_created.as_millis(), 1000);
885        assert_eq!(retrieved.metadata.time_last_used.as_millis(), 2000);
886        assert_eq!(retrieved.metadata.time_last_modified.as_millis(), 3000);
887        assert_eq!(retrieved.metadata.times_used, 4);
888        assert_eq!(retrieved.metadata.sync_change_counter, 2);
889
890        Ok(())
891    }
892
893    #[test]
894    fn test_address_add_with_meta_sanitizes_out_of_range_timestamps() -> Result<()> {
895        let db = new_mem_db();
896
897        // Negative, and the value from bug 2066257 - a negative microsecond
898        // timestamp that a JS consumer already reinterpreted as a u64 and
899        // divided by 1000, so it reaches us as a huge positive number. Both are
900        // "we don't know when", and a `.max(0)` would only catch the first.
901        for (guid, out_of_range) in [("abc", -1), ("def", 18446744071857664)] {
902            let meta = AddressMeta {
903                guid: guid.to_string(),
904                time_created: out_of_range,
905                time_last_used: Some(out_of_range),
906                time_last_modified: out_of_range,
907                times_used: 0,
908                sync_change_counter: 0,
909            };
910            add_address_with_meta(&db, test_fields("123 Main Street"), meta)?;
911
912            let retrieved = get_address(&db, &Guid::new(guid))?;
913            assert_eq!(
914                retrieved.metadata.time_created.as_millis(),
915                0,
916                "{out_of_range} survived"
917            );
918            assert_eq!(retrieved.metadata.time_last_used.as_millis(), 0);
919            assert_eq!(retrieved.metadata.time_last_modified.as_millis(), 0);
920        }
921
922        Ok(())
923    }
924
925    /// Surface 2: a value already on disk, put there before the import path
926    /// sanitized anything. Reading it must repair rather than propagate it.
927    #[test]
928    fn test_address_from_row_sanitizes_corrupt_timestamps() -> Result<()> {
929        let db = new_mem_db();
930
931        let address = add_address(&db, test_fields("123 Main Street"))?;
932        db.execute(
933            // Three shapes that are not representable dates: the u64-reinterpreted
934            // value from bug 2066257, a raw negative, and MAX_DATE_MS + 1.
935            "UPDATE addresses_data
936             SET time_created = 18446744071857664,
937                 time_last_used = -1,
938                 time_last_modified = 8640000000000001
939             WHERE guid = :guid",
940            rusqlite::named_params! { ":guid": address.guid },
941        )?;
942
943        let retrieved = get_address(&db, &address.guid)?;
944        assert_eq!(retrieved.metadata.time_created.as_millis(), 0);
945        assert_eq!(retrieved.metadata.time_last_used.as_millis(), 0);
946        assert_eq!(retrieved.metadata.time_last_modified.as_millis(), 0);
947
948        Ok(())
949    }
950
951    #[test]
952    fn test_address_update_with_meta_keeps_supplied_counter() -> Result<()> {
953        let db = new_mem_db();
954
955        add_address_with_meta(&db, test_fields("123 Main Street"), test_meta("abc", 0))?;
956
957        // the supplied counter must be applied, not the one already in the row.
958        update_address_with_meta(&db, test_fields("456 Second Avenue"), test_meta("abc", 1))?;
959
960        let retrieved = get_address(&db, &Guid::new("abc"))?;
961        assert_eq!(retrieved.street_address, "456 Second Avenue");
962        assert_eq!(retrieved.metadata.sync_change_counter, 1);
963
964        // and back down again.
965        update_address_with_meta(&db, test_fields("456 Second Avenue"), test_meta("abc", 0))?;
966        assert_eq!(
967            get_address(&db, &Guid::new("abc"))?
968                .metadata
969                .sync_change_counter,
970            0
971        );
972
973        Ok(())
974    }
975
976    #[test]
977    fn test_address_update_with_meta_errors_when_missing() -> Result<()> {
978        let db = new_mem_db();
979
980        let result =
981            update_address_with_meta(&db, test_fields("123 Main Street"), test_meta("abc", 3));
982        assert!(matches!(result, Err(Error::NoSuchRecord(guid)) if guid == "abc"));
983        assert!(get_address(&db, &Guid::new("abc")).is_err());
984
985        Ok(())
986    }
987
988    #[test]
989    fn test_address_add_many_with_meta_isolates_failures() -> Result<()> {
990        let db = new_mem_db();
991
992        // the second entry has an empty guid, which the `addresses_data` CHECK
993        // constraint rejects. The others must still be inserted.
994        let results = add_many_addresses_with_meta(
995            &db,
996            vec![
997                UpdatableAddressFieldsWithMeta {
998                    fields: test_fields("1 First Street"),
999                    meta: test_meta("aaa", 1),
1000                },
1001                UpdatableAddressFieldsWithMeta {
1002                    fields: test_fields("2 Second Street"),
1003                    meta: test_meta("", 1),
1004                },
1005                UpdatableAddressFieldsWithMeta {
1006                    fields: test_fields("3 Third Street"),
1007                    meta: test_meta("ccc", 1),
1008                },
1009            ],
1010        )?;
1011
1012        assert_eq!(results.len(), 3);
1013        assert!(results[0].is_ok());
1014        assert!(results[1].is_err());
1015        assert!(results[2].is_ok());
1016
1017        assert_eq!(get_all_addresses(&db)?.len(), 2);
1018        assert_eq!(get_address(&db, &Guid::new("aaa"))?.metadata.times_used, 4);
1019
1020        Ok(())
1021    }
1022
1023    #[test]
1024    fn test_delete_all_addresses_allows_a_reimport() -> Result<()> {
1025        let db = new_mem_db();
1026
1027        // A tombstone left by an earlier import, and a record sharing no guid
1028        // with it.
1029        add_many_address_tombstones(&db, vec![("gone".to_string(), 1234)])?;
1030        let address = add_address(&db, UpdatableAddressFields::default())?;
1031
1032        delete_all_addresses(&db)?;
1033        assert_eq!(get_all_addresses(&db)?.len(), 0);
1034        let tombstones: i64 =
1035            db.query_row("SELECT COUNT(*) FROM addresses_tombstones", [], |row| {
1036                row.get(0)
1037            })?;
1038        assert_eq!(tombstones, 0, "tombstones are cleared with the records");
1039
1040        // The point of clearing them: re-importing the same guids succeeds,
1041        // where the insert trigger would reject a guid still tombstoned.
1042        let results = add_many_addresses_with_meta(
1043            &db,
1044            vec![
1045                UpdatableAddressFieldsWithMeta {
1046                    fields: UpdatableAddressFields::default(),
1047                    meta: AddressMeta {
1048                        guid: address.guid.to_string(),
1049                        ..Default::default()
1050                    },
1051                },
1052                UpdatableAddressFieldsWithMeta {
1053                    fields: UpdatableAddressFields::default(),
1054                    meta: AddressMeta {
1055                        guid: "gone".to_string(),
1056                        ..Default::default()
1057                    },
1058                },
1059            ],
1060        )?;
1061        assert!(
1062            results.iter().all(|r| r.is_ok()),
1063            "a previously tombstoned guid can be re-imported: {results:?}"
1064        );
1065
1066        Ok(())
1067    }
1068
1069    #[test]
1070    fn test_address_add_many_tombstones() -> Result<()> {
1071        let db = new_mem_db();
1072
1073        let results = add_many_address_tombstones(&db, vec![("aaa".to_string(), 1234)])?;
1074        assert_eq!(results.len(), 1);
1075        assert!(results[0].is_ok());
1076
1077        // the supplied deletion time is used rather than being stamped as now.
1078        let time_deleted: i64 = db.query_row(
1079            "SELECT time_deleted FROM addresses_tombstones WHERE guid = 'aaa'",
1080            [],
1081            |row| row.get(0),
1082        )?;
1083        assert_eq!(time_deleted, 1234);
1084
1085        Ok(())
1086    }
1087
1088    #[test]
1089    fn test_address_add_many_tombstones_rejects_live_guid() -> Result<()> {
1090        let db = new_mem_db();
1091
1092        add_address_with_meta(&db, test_fields("123 Main Street"), test_meta("abc", 0))?;
1093
1094        // a guid cannot be in both `addresses_data` and `addresses_tombstones`;
1095        // the trigger enforcing that must not take the rest of the batch down.
1096        let results = add_many_address_tombstones(
1097            &db,
1098            vec![("abc".to_string(), 1234), ("ddd".to_string(), 5678)],
1099        )?;
1100
1101        assert_eq!(results.len(), 2);
1102        assert!(results[0].is_err());
1103        assert!(results[1].is_ok());
1104
1105        // the rejected tombstone must not have been committed anyway - see
1106        // `with_savepoint`.
1107        assert_eq!(count_tombstones(&db, "abc")?, 0);
1108        assert!(get_address(&db, &Guid::new("abc")).is_ok());
1109        assert_eq!(count_tombstones(&db, "ddd")?, 1);
1110
1111        Ok(())
1112    }
1113
1114    #[test]
1115    fn test_address_add_many_with_meta_rejects_deleted_guid() -> Result<()> {
1116        let db = new_mem_db();
1117
1118        add_many_address_tombstones(&db, vec![("aaa".to_string(), 1234)])?;
1119
1120        // the other side of the same invariant: a guid in
1121        // `addresses_tombstones` cannot be inserted into `addresses_data`.
1122        let results = add_many_addresses_with_meta(
1123            &db,
1124            vec![
1125                UpdatableAddressFieldsWithMeta {
1126                    fields: test_fields("1 First Street"),
1127                    meta: test_meta("aaa", 1),
1128                },
1129                UpdatableAddressFieldsWithMeta {
1130                    fields: test_fields("2 Second Street"),
1131                    meta: test_meta("bbb", 1),
1132                },
1133            ],
1134        )?;
1135
1136        assert_eq!(results.len(), 2);
1137        assert!(results[0].is_err());
1138        assert!(results[1].is_ok());
1139
1140        assert!(get_address(&db, &Guid::new("aaa")).is_err());
1141        assert_eq!(get_all_addresses(&db)?.len(), 1);
1142
1143        Ok(())
1144    }
1145
1146    fn count_tombstones(conn: &Connection, guid: &str) -> Result<i64> {
1147        Ok(conn.query_row(
1148            "SELECT COUNT(*) FROM addresses_tombstones WHERE guid = :guid",
1149            rusqlite::named_params! { ":guid": guid },
1150            |row| row.get(0),
1151        )?)
1152    }
1153}