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::{InternalAddress, UpdatableAddressFields},
9        Metadata,
10    },
11    schema::{ADDRESS_COMMON_COLS, ADDRESS_COMMON_VALS},
12};
13use crate::error::*;
14
15use rusqlite::{Connection, Transaction};
16use sync_guid::Guid;
17use types::Timestamp;
18
19pub(crate) fn add_address(
20    conn: &Connection,
21    new: UpdatableAddressFields,
22) -> Result<InternalAddress> {
23    let tx = conn.unchecked_transaction()?;
24    let now = Timestamp::now();
25
26    // We return an InternalAddress, so set it up first, including the missing
27    // fields, before we insert it.
28    let address = InternalAddress {
29        guid: Guid::random(),
30        name: new.name,
31        organization: new.organization,
32        street_address: new.street_address,
33        address_level3: new.address_level3,
34        address_level2: new.address_level2,
35        address_level1: new.address_level1,
36        postal_code: new.postal_code,
37        country: new.country,
38        tel: new.tel,
39        email: new.email,
40        metadata: Metadata {
41            time_created: now,
42            time_last_modified: now,
43            ..Default::default()
44        },
45    };
46    add_internal_address(&tx, &address)?;
47    tx.commit()?;
48    Ok(address)
49}
50
51pub(crate) fn add_internal_address(tx: &Transaction<'_>, address: &InternalAddress) -> Result<()> {
52    tx.execute(
53        &format!(
54            "INSERT INTO addresses_data (
55                {common_cols},
56                sync_change_counter
57            ) VALUES (
58                {common_vals},
59                :sync_change_counter
60            )",
61            common_cols = ADDRESS_COMMON_COLS,
62            common_vals = ADDRESS_COMMON_VALS,
63        ),
64        rusqlite::named_params! {
65            ":guid": address.guid,
66            ":name": address.name,
67            ":organization": address.organization,
68            ":street_address": address.street_address,
69            ":address_level3": address.address_level3,
70            ":address_level2": address.address_level2,
71            ":address_level1": address.address_level1,
72            ":postal_code": address.postal_code,
73            ":country": address.country,
74            ":tel": address.tel,
75            ":email": address.email,
76            ":time_created": address.metadata.time_created,
77            ":time_last_used": address.metadata.time_last_used,
78            ":time_last_modified": address.metadata.time_last_modified,
79            ":times_used": address.metadata.times_used,
80            ":sync_change_counter": address.metadata.sync_change_counter,
81        },
82    )?;
83    Ok(())
84}
85
86pub(crate) fn get_address(conn: &Connection, guid: &Guid) -> Result<InternalAddress> {
87    let sql = format!(
88        "SELECT
89            {common_cols},
90            sync_change_counter
91        FROM addresses_data
92        WHERE guid = :guid",
93        common_cols = ADDRESS_COMMON_COLS
94    );
95    conn.query_row(&sql, [guid], InternalAddress::from_row)
96        .map_err(|e| match e {
97            rusqlite::Error::QueryReturnedNoRows => Error::NoSuchRecord(guid.to_string()),
98            e => e.into(),
99        })
100}
101
102pub(crate) fn get_all_addresses(conn: &Connection) -> Result<Vec<InternalAddress>> {
103    let sql = format!(
104        "SELECT
105            {common_cols},
106            sync_change_counter
107        FROM addresses_data",
108        common_cols = ADDRESS_COMMON_COLS
109    );
110
111    let mut stmt = conn.prepare(&sql)?;
112    let addresses = stmt
113        .query_map([], InternalAddress::from_row)?
114        .collect::<std::result::Result<Vec<InternalAddress>, _>>()?;
115    Ok(addresses)
116}
117
118pub(crate) fn count_all_addresses(conn: &Connection) -> Result<i64> {
119    let sql = "SELECT COUNT(*)
120        FROM addresses_data";
121
122    let mut stmt = conn.prepare(sql)?;
123    let count: i64 = stmt.query_row([], |row| row.get(0))?;
124    Ok(count)
125}
126
127/// Updates just the "updatable" columns - suitable for exposure as a public
128/// API.
129pub(crate) fn update_address(
130    conn: &Connection,
131    guid: &Guid,
132    address: &UpdatableAddressFields,
133) -> Result<()> {
134    let tx = conn.unchecked_transaction()?;
135    tx.execute(
136        "UPDATE addresses_data
137        SET name                = :name,
138            organization        = :organization,
139            street_address      = :street_address,
140            address_level3      = :address_level3,
141            address_level2      = :address_level2,
142            address_level1      = :address_level1,
143            postal_code         = :postal_code,
144            country             = :country,
145            tel                 = :tel,
146            email               = :email,
147            sync_change_counter = sync_change_counter + 1
148        WHERE guid              = :guid",
149        rusqlite::named_params! {
150            ":name": address.name,
151            ":organization": address.organization,
152            ":street_address": address.street_address,
153            ":address_level3": address.address_level3,
154            ":address_level2": address.address_level2,
155            ":address_level1": address.address_level1,
156            ":postal_code": address.postal_code,
157            ":country": address.country,
158            ":tel": address.tel,
159            ":email": address.email,
160            ":guid": guid,
161        },
162    )?;
163
164    tx.commit()?;
165    Ok(())
166}
167
168/// Updates all fields including metadata - although the change counter gets
169/// slightly special treatment (eg, when called by Sync we don't want the
170/// change counter incremented)
171pub(crate) fn update_internal_address(
172    tx: &Transaction<'_>,
173    address: &InternalAddress,
174    flag_as_changed: bool,
175) -> Result<()> {
176    let change_counter_increment = flag_as_changed as u32; // will be 1 or 0
177    let rows_changed = tx.execute(
178        "UPDATE addresses_data SET
179            name                = :name,
180            organization        = :organization,
181            street_address      = :street_address,
182            address_level3      = :address_level3,
183            address_level2      = :address_level2,
184            address_level1      = :address_level1,
185            postal_code         = :postal_code,
186            country             = :country,
187            tel                 = :tel,
188            email               = :email,
189            time_created        = :time_created,
190            time_last_used      = :time_last_used,
191            time_last_modified  = :time_last_modified,
192            times_used          = :times_used,
193            sync_change_counter = sync_change_counter + :change_incr
194        WHERE guid              = :guid",
195        rusqlite::named_params! {
196            ":name": address.name,
197            ":organization": address.organization,
198            ":street_address": address.street_address,
199            ":address_level3": address.address_level3,
200            ":address_level2": address.address_level2,
201            ":address_level1": address.address_level1,
202            ":postal_code": address.postal_code,
203            ":country": address.country,
204            ":tel": address.tel,
205            ":email": address.email,
206            ":time_created": address.metadata.time_created,
207            ":time_last_used": address.metadata.time_last_used,
208            ":time_last_modified": address.metadata.time_last_modified,
209            ":times_used": address.metadata.times_used,
210            ":change_incr": change_counter_increment,
211            ":guid": address.guid,
212        },
213    )?;
214    // Something went badly wrong if we are asking to update a row that doesn't
215    // exist, or somehow we updated more than 1!
216    assert_eq!(rows_changed, 1);
217    Ok(())
218}
219
220pub(crate) fn delete_address(conn: &Connection, guid: &Guid) -> Result<bool> {
221    let tx = conn.unchecked_transaction()?;
222
223    // execute returns how many rows were affected.
224    let exists = tx.execute(
225        "DELETE FROM addresses_data
226            WHERE guid = :guid",
227        rusqlite::named_params! {
228            ":guid": guid,
229        },
230    )? != 0;
231    tx.commit()?;
232    Ok(exists)
233}
234
235pub fn touch(conn: &Connection, guid: &Guid) -> Result<()> {
236    let tx = conn.unchecked_transaction()?;
237    let now_ms = Timestamp::now();
238
239    tx.execute(
240        "UPDATE addresses_data
241        SET time_last_used              = :time_last_used,
242            times_used                  = times_used + 1,
243            sync_change_counter         = sync_change_counter + 1
244        WHERE guid                      = :guid",
245        rusqlite::named_params! {
246            ":time_last_used": now_ms,
247            ":guid": guid,
248        },
249    )?;
250
251    tx.commit()?;
252    Ok(())
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258    use crate::db::{schema::create_empty_sync_temp_tables, test::new_mem_db};
259    use sync_guid::Guid;
260    use types::Timestamp;
261
262    #[allow(dead_code)]
263    fn get_all(
264        conn: &Connection,
265        table_name: String,
266    ) -> rusqlite::Result<Vec<String>, rusqlite::Error> {
267        let mut stmt = conn.prepare(&format!(
268            "SELECT guid FROM {table_name}",
269            table_name = table_name
270        ))?;
271        let rows = stmt.query_map([], |row| row.get(0))?;
272
273        let mut guids = Vec::new();
274        for guid_result in rows {
275            guids.push(guid_result?);
276        }
277
278        Ok(guids)
279    }
280
281    fn insert_tombstone_record(
282        conn: &Connection,
283        guid: String,
284    ) -> rusqlite::Result<usize, rusqlite::Error> {
285        conn.execute(
286            "INSERT INTO addresses_tombstones (
287                guid,
288                time_deleted
289            ) VALUES (
290                :guid,
291                :time_deleted
292            )",
293            rusqlite::named_params! {
294                ":guid": guid,
295                ":time_deleted": Timestamp::now(),
296            },
297        )
298    }
299
300    #[test]
301    fn test_address_create_and_read() {
302        let db = new_mem_db();
303
304        let saved_address = add_address(
305            &db,
306            UpdatableAddressFields {
307                name: "jane doe".to_string(),
308                street_address: "123 Main Street".to_string(),
309                address_level2: "Seattle, WA".to_string(),
310                country: "United States".to_string(),
311
312                ..UpdatableAddressFields::default()
313            },
314        )
315        .expect("should contain saved address");
316
317        // check that the add function populated the guid field
318        assert_ne!(Guid::default(), saved_address.guid);
319
320        // check that the time created and time last modified were set
321        assert_ne!(0, saved_address.metadata.time_created.as_millis());
322        assert_ne!(0, saved_address.metadata.time_last_modified.as_millis());
323
324        assert_eq!(0, saved_address.metadata.sync_change_counter);
325
326        // get created address
327        let retrieved_address = get_address(&db, &saved_address.guid)
328            .expect("should contain optional retrieved address");
329        assert_eq!(saved_address.guid, retrieved_address.guid);
330        assert_eq!(saved_address.name, retrieved_address.name);
331        assert_eq!(
332            saved_address.street_address,
333            retrieved_address.street_address
334        );
335        assert_eq!(
336            saved_address.address_level2,
337            retrieved_address.address_level2
338        );
339        assert_eq!(saved_address.country, retrieved_address.country);
340
341        // converting the created record into a tombstone to check that it's not returned on a second `get_address` call
342        let delete_result = delete_address(&db, &saved_address.guid);
343        assert!(delete_result.is_ok());
344        assert!(delete_result.unwrap());
345
346        assert!(get_address(&db, &saved_address.guid).is_err());
347    }
348
349    #[test]
350    fn test_address_missing_guid() {
351        let db = new_mem_db();
352        let guid = Guid::random();
353        let result = get_address(&db, &guid);
354
355        assert_eq!(
356            result.unwrap_err().to_string(),
357            Error::NoSuchRecord(guid.to_string()).to_string()
358        );
359    }
360
361    #[test]
362    fn test_address_read_all() {
363        let db = new_mem_db();
364
365        let saved_address = add_address(
366            &db,
367            UpdatableAddressFields {
368                name: "jane doe".to_string(),
369                street_address: "123 Second Avenue".to_string(),
370                address_level2: "Chicago, IL".to_string(),
371                country: "United States".to_string(),
372
373                ..UpdatableAddressFields::default()
374            },
375        )
376        .expect("should contain saved address");
377
378        let saved_address2 = add_address(
379            &db,
380            UpdatableAddressFields {
381                name: "john deer".to_string(),
382                street_address: "123 First Avenue".to_string(),
383                address_level2: "Los Angeles, CA".to_string(),
384                country: "United States".to_string(),
385
386                ..UpdatableAddressFields::default()
387            },
388        )
389        .expect("should contain saved address");
390
391        // creating a third address with a tombstone to ensure it's not returned
392        let saved_address3 = add_address(
393            &db,
394            UpdatableAddressFields {
395                name: "abraham lincoln".to_string(),
396                street_address: "1600 Pennsylvania Ave NW".to_string(),
397                address_level2: "Washington, DC".to_string(),
398                country: "United States".to_string(),
399
400                ..UpdatableAddressFields::default()
401            },
402        )
403        .expect("should contain saved address");
404
405        let delete_result = delete_address(&db, &saved_address3.guid);
406        assert!(delete_result.is_ok());
407        assert!(delete_result.unwrap());
408
409        let retrieved_addresses =
410            get_all_addresses(&db).expect("Should contain all saved addresses");
411
412        assert!(!retrieved_addresses.is_empty());
413        let expected_number_of_addresses = 2;
414        assert_eq!(expected_number_of_addresses, retrieved_addresses.len());
415
416        let address_count = count_all_addresses(&db).expect("Should count all saved addresses");
417        assert_eq!(expected_number_of_addresses, address_count as usize);
418
419        let retrieved_address_guids = [
420            retrieved_addresses[0].guid.as_str(),
421            retrieved_addresses[1].guid.as_str(),
422        ];
423        assert!(retrieved_address_guids.contains(&saved_address.guid.as_str()));
424        assert!(retrieved_address_guids.contains(&saved_address2.guid.as_str()));
425    }
426
427    #[test]
428    fn test_address_update() {
429        let db = new_mem_db();
430
431        let saved_address = add_address(
432            &db,
433            UpdatableAddressFields {
434                name: "john doe".to_string(),
435                street_address: "1300 Broadway".to_string(),
436                address_level2: "New York, NY".to_string(),
437                country: "United States".to_string(),
438
439                ..UpdatableAddressFields::default()
440            },
441        )
442        .expect("should contain saved address");
443        // change_counter starts at 0
444        assert_eq!(0, saved_address.metadata.sync_change_counter);
445
446        let expected_name = "john paul deer".to_string();
447        let update_result = update_address(
448            &db,
449            &saved_address.guid,
450            &UpdatableAddressFields {
451                name: expected_name.clone(),
452                organization: "".to_string(),
453                street_address: "123 First Avenue".to_string(),
454                address_level3: "".to_string(),
455                address_level2: "Denver, CO".to_string(),
456                address_level1: "".to_string(),
457                postal_code: "".to_string(),
458                country: "United States".to_string(),
459                tel: "".to_string(),
460                email: "".to_string(),
461            },
462        );
463        assert!(update_result.is_ok());
464
465        let updated_address =
466            get_address(&db, &saved_address.guid).expect("should contain optional updated address");
467
468        assert_eq!(saved_address.guid, updated_address.guid);
469        assert_eq!(expected_name, updated_address.name);
470
471        //check that the sync_change_counter was incremented
472        assert_eq!(1, updated_address.metadata.sync_change_counter);
473    }
474
475    #[test]
476    fn test_address_update_internal_address() -> Result<()> {
477        let mut db = new_mem_db();
478        let tx = db.transaction()?;
479
480        let guid = Guid::random();
481        add_internal_address(
482            &tx,
483            &InternalAddress {
484                guid: guid.clone(),
485                name: "john paul deer".to_string(),
486                organization: "".to_string(),
487                street_address: "123 First Avenue".to_string(),
488                address_level3: "".to_string(),
489                address_level2: "Denver, CO".to_string(),
490                address_level1: "".to_string(),
491                postal_code: "".to_string(),
492                country: "United States".to_string(),
493                tel: "".to_string(),
494                email: "".to_string(),
495                ..Default::default()
496            },
497        )?;
498
499        let expected_name = "john paul dear";
500        update_internal_address(
501            &tx,
502            &InternalAddress {
503                guid: guid.clone(),
504                name: expected_name.to_string(),
505                organization: "".to_string(),
506                street_address: "123 First Avenue".to_string(),
507                address_level3: "".to_string(),
508                address_level2: "Denver, CO".to_string(),
509                address_level1: "".to_string(),
510                postal_code: "".to_string(),
511                country: "United States".to_string(),
512                tel: "".to_string(),
513                email: "".to_string(),
514                ..Default::default()
515            },
516            false,
517        )?;
518
519        let record_exists: bool = tx.query_row(
520            "SELECT EXISTS (
521                SELECT 1
522                FROM addresses_data
523                WHERE guid = :guid
524                AND name = :name
525                AND sync_change_counter = 0
526            )",
527            [&guid.to_string(), &expected_name.to_string()],
528            |row| row.get(0),
529        )?;
530        assert!(record_exists);
531
532        Ok(())
533    }
534
535    #[test]
536    fn test_address_delete() {
537        fn num_tombstones(conn: &Connection) -> u32 {
538            let stmt = "SELECT COUNT(*) from addresses_tombstones";
539            conn.query_row(stmt, [], |row| Ok(row.get::<_, u32>(0).unwrap()))
540                .unwrap()
541        }
542
543        let db = new_mem_db();
544        create_empty_sync_temp_tables(&db).expect("should create temp tables");
545
546        let saved_address = add_address(
547            &db,
548            UpdatableAddressFields {
549                name: "jane doe".to_string(),
550                street_address: "123 Second Avenue".to_string(),
551                address_level2: "Chicago, IL".to_string(),
552                country: "United States".to_string(),
553                ..UpdatableAddressFields::default()
554            },
555        )
556        .expect("first create should work");
557
558        delete_address(&db, &saved_address.guid).expect("delete should work");
559        // should be no tombstone as it wasn't in the mirror.
560        assert_eq!(num_tombstones(&db), 0);
561
562        // do it again, but with it in the mirror.
563        let saved_address = add_address(
564            &db,
565            UpdatableAddressFields {
566                name: "jane doe".to_string(),
567                street_address: "123 Second Avenue".to_string(),
568                address_level2: "Chicago, IL".to_string(),
569                country: "United States".to_string(),
570                ..UpdatableAddressFields::default()
571            },
572        )
573        .expect("create 2nd address should work");
574        db.execute(
575            &format!(
576                "INSERT INTO addresses_mirror (guid, payload) VALUES ('{}', 'whatever')",
577                saved_address.guid,
578            ),
579            [],
580        )
581        .expect("manual insert into mirror");
582        delete_address(&db, &saved_address.guid).expect("2nd delete");
583        assert_eq!(num_tombstones(&db), 1);
584    }
585
586    #[test]
587    fn test_address_trigger_on_create() {
588        let db = new_mem_db();
589        let tx = db.unchecked_transaction().expect("should get a tx");
590        let guid = Guid::random();
591
592        // create a tombstone record
593        let tombstone_result = insert_tombstone_record(&db, guid.to_string());
594        assert!(tombstone_result.is_ok());
595
596        // create a new address with the tombstone's guid
597        let address = InternalAddress {
598            guid,
599            name: "jane doe".to_string(),
600            street_address: "123 Second Avenue".to_string(),
601            address_level2: "Chicago, IL".to_string(),
602            country: "United States".to_string(),
603            ..Default::default()
604        };
605
606        let add_address_result = add_internal_address(&tx, &address);
607        assert!(add_address_result.is_err());
608
609        let expected_error_message = "guid exists in `addresses_tombstones`";
610        assert!(add_address_result
611            .unwrap_err()
612            .to_string()
613            .contains(expected_error_message))
614    }
615
616    #[test]
617    fn test_address_trigger_on_delete() {
618        let db = new_mem_db();
619        let tx = db.unchecked_transaction().expect("should get a tx");
620        let guid = Guid::random();
621
622        // create an address
623        let address = InternalAddress {
624            guid,
625            name: "jane doe".to_string(),
626            street_address: "123 Second Avenue".to_string(),
627            address_level2: "Chicago, IL".to_string(),
628            country: "United States".to_string(),
629            ..Default::default()
630        };
631
632        let add_address_result = add_internal_address(&tx, &address);
633        assert!(add_address_result.is_ok());
634
635        // create a tombstone record with the same guid
636        let tombstone_result = insert_tombstone_record(&db, address.guid.to_string());
637        assert!(tombstone_result.is_err());
638
639        let expected_error_message = "guid exists in `addresses_data`";
640        assert_eq!(
641            expected_error_message,
642            tombstone_result.unwrap_err().to_string()
643        );
644    }
645
646    #[test]
647    fn test_address_touch() -> Result<()> {
648        let db = new_mem_db();
649        let saved_address = add_address(
650            &db,
651            UpdatableAddressFields {
652                name: "jane doe".to_string(),
653                street_address: "123 Second Avenue".to_string(),
654                address_level2: "Chicago, IL".to_string(),
655                country: "United States".to_string(),
656
657                ..UpdatableAddressFields::default()
658            },
659        )?;
660
661        assert_eq!(saved_address.metadata.sync_change_counter, 0);
662        assert_eq!(saved_address.metadata.times_used, 0);
663
664        touch(&db, &saved_address.guid)?;
665
666        let touched_address = get_address(&db, &saved_address.guid)?;
667
668        assert_eq!(touched_address.metadata.sync_change_counter, 1);
669        assert_eq!(touched_address.metadata.times_used, 1);
670
671        Ok(())
672    }
673}