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