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