1use crate::db::{
7 models::{
8 address::{
9 AddressMeta, InternalAddress, UpdatableAddressFields, UpdatableAddressFieldsWithMeta,
10 },
11 Metadata,
12 },
13 schema::{ADDRESS_COMMON_COLS, ADDRESS_COMMON_VALS},
14};
15use crate::db::{timestamp_from_millis, with_savepoint, CounterUpdate};
16use crate::error::*;
17
18use rusqlite::{Connection, Transaction};
19use sync_guid::Guid;
20use types::Timestamp;
21
22pub(crate) fn add_address(
23 conn: &Connection,
24 new: UpdatableAddressFields,
25) -> Result<InternalAddress> {
26 let tx = conn.unchecked_transaction()?;
27 let now = Timestamp::now();
28
29 let address = InternalAddress {
32 guid: Guid::random(),
33 name: new.name,
34 organization: new.organization,
35 street_address: new.street_address,
36 address_level3: new.address_level3,
37 address_level2: new.address_level2,
38 address_level1: new.address_level1,
39 postal_code: new.postal_code,
40 country: new.country,
41 tel: new.tel,
42 email: new.email,
43 metadata: Metadata {
44 time_created: now,
45 time_last_modified: now,
46 ..Default::default()
47 },
48 };
49 add_internal_address(&tx, &address)?;
50 tx.commit()?;
51 Ok(address)
52}
53
54pub(crate) fn add_address_with_meta(
59 conn: &Connection,
60 fields: UpdatableAddressFields,
61 meta: AddressMeta,
62) -> Result<InternalAddress> {
63 let tx = conn.unchecked_transaction()?;
64 let address = internal_address_from_meta(fields, &meta);
65 add_internal_address(&tx, &address)?;
66 tx.commit()?;
67 Ok(address)
68}
69
70pub(crate) fn add_many_addresses_with_meta(
74 conn: &Connection,
75 entries: Vec<UpdatableAddressFieldsWithMeta>,
76) -> Result<Vec<std::result::Result<InternalAddress, String>>> {
77 let tx = conn.unchecked_transaction()?;
78 let mut results = Vec::with_capacity(entries.len());
79 for entry in entries {
80 let address = internal_address_from_meta(entry.fields, &entry.meta);
81 match with_savepoint(&tx, || add_internal_address(&tx, &address))? {
82 Ok(()) => results.push(Ok(address)),
83 Err(e) => results.push(Err(e.to_string())),
84 }
85 }
86 tx.commit()?;
87 Ok(results)
88}
89
90pub(crate) fn delete_all_addresses(conn: &Connection) -> Result<()> {
97 let tx = conn.unchecked_transaction()?;
98 tx.execute("DELETE FROM addresses_data", [])?;
99 tx.execute("DELETE FROM addresses_tombstones", [])?;
101 tx.commit()?;
102 Ok(())
103}
104
105pub(crate) fn add_many_address_tombstones(
111 conn: &Connection,
112 tombstones: Vec<(String, i64)>,
113) -> Result<Vec<std::result::Result<String, String>>> {
114 let tx = conn.unchecked_transaction()?;
115 let mut results = Vec::with_capacity(tombstones.len());
116 for (guid, time_deleted) in tombstones {
117 let inserted = with_savepoint(&tx, || {
118 tx.execute(
119 "INSERT INTO addresses_tombstones (guid, time_deleted)
120 VALUES (:guid, :time_deleted)",
121 rusqlite::named_params! {
122 ":guid": &guid,
123 ":time_deleted": timestamp_from_millis(time_deleted),
124 },
125 )?;
126 Ok(())
127 })?;
128 match inserted {
129 Ok(()) => results.push(Ok(guid)),
130 Err(e) => results.push(Err(e.to_string())),
131 }
132 }
133 tx.commit()?;
134 Ok(results)
135}
136
137fn internal_address_from_meta(
138 fields: UpdatableAddressFields,
139 meta: &AddressMeta,
140) -> InternalAddress {
141 InternalAddress {
142 guid: Guid::new(&meta.guid),
143 name: fields.name,
144 organization: fields.organization,
145 street_address: fields.street_address,
146 address_level3: fields.address_level3,
147 address_level2: fields.address_level2,
148 address_level1: fields.address_level1,
149 postal_code: fields.postal_code,
150 country: fields.country,
151 tel: fields.tel,
152 email: fields.email,
153 metadata: Metadata {
154 time_created: timestamp_from_millis(meta.time_created),
155 time_last_used: timestamp_from_millis(meta.time_last_used.unwrap_or(0)),
156 time_last_modified: timestamp_from_millis(meta.time_last_modified),
157 times_used: meta.times_used,
158 sync_change_counter: meta.sync_change_counter,
159 },
160 }
161}
162
163pub(crate) fn update_address_with_meta(
169 conn: &Connection,
170 fields: UpdatableAddressFields,
171 meta: AddressMeta,
172) -> Result<()> {
173 let tx = conn.unchecked_transaction()?;
174
175 let address = internal_address_from_meta(fields, &meta);
176 let exists: bool = tx.query_row(
179 "SELECT EXISTS(SELECT 1 FROM addresses_data WHERE guid = :guid)",
180 rusqlite::named_params! { ":guid": address.guid },
181 |row| row.get(0),
182 )?;
183 if !exists {
184 return Err(Error::NoSuchRecord(address.guid.to_string()));
185 }
186 update_internal_address(
187 &tx,
188 &address,
189 CounterUpdate::Set(address.metadata.sync_change_counter),
190 )?;
191 tx.commit()?;
192 Ok(())
193}
194
195pub(crate) fn add_internal_address(tx: &Transaction<'_>, address: &InternalAddress) -> Result<()> {
196 tx.execute(
197 &format!(
198 "INSERT INTO addresses_data (
199 {common_cols},
200 sync_change_counter
201 ) VALUES (
202 {common_vals},
203 :sync_change_counter
204 )",
205 common_cols = ADDRESS_COMMON_COLS,
206 common_vals = ADDRESS_COMMON_VALS,
207 ),
208 rusqlite::named_params! {
209 ":guid": address.guid,
210 ":name": address.name,
211 ":organization": address.organization,
212 ":street_address": address.street_address,
213 ":address_level3": address.address_level3,
214 ":address_level2": address.address_level2,
215 ":address_level1": address.address_level1,
216 ":postal_code": address.postal_code,
217 ":country": address.country,
218 ":tel": address.tel,
219 ":email": address.email,
220 ":time_created": address.metadata.time_created,
221 ":time_last_used": address.metadata.time_last_used,
222 ":time_last_modified": address.metadata.time_last_modified,
223 ":times_used": address.metadata.times_used,
224 ":sync_change_counter": address.metadata.sync_change_counter,
225 },
226 )?;
227 Ok(())
228}
229
230pub(crate) fn get_address(conn: &Connection, guid: &Guid) -> Result<InternalAddress> {
231 let sql = format!(
232 "SELECT
233 {common_cols},
234 sync_change_counter
235 FROM addresses_data
236 WHERE guid = :guid",
237 common_cols = ADDRESS_COMMON_COLS
238 );
239 conn.query_row(&sql, [guid], InternalAddress::from_row)
240 .map_err(|e| match e {
241 rusqlite::Error::QueryReturnedNoRows => Error::NoSuchRecord(guid.to_string()),
242 e => e.into(),
243 })
244}
245
246pub(crate) fn get_all_addresses(conn: &Connection) -> Result<Vec<InternalAddress>> {
247 let sql = format!(
248 "SELECT
249 {common_cols},
250 sync_change_counter
251 FROM addresses_data",
252 common_cols = ADDRESS_COMMON_COLS
253 );
254
255 let mut stmt = conn.prepare(&sql)?;
256 let addresses = stmt
257 .query_map([], InternalAddress::from_row)?
258 .collect::<std::result::Result<Vec<InternalAddress>, _>>()?;
259 Ok(addresses)
260}
261
262pub(crate) fn count_all_addresses(conn: &Connection) -> Result<i64> {
263 let sql = "SELECT COUNT(*)
264 FROM addresses_data";
265
266 let mut stmt = conn.prepare(sql)?;
267 let count: i64 = stmt.query_row([], |row| row.get(0))?;
268 Ok(count)
269}
270
271pub(crate) fn update_address(
274 conn: &Connection,
275 guid: &Guid,
276 address: &UpdatableAddressFields,
277) -> Result<()> {
278 let tx = conn.unchecked_transaction()?;
279 tx.execute(
280 "UPDATE addresses_data
281 SET name = :name,
282 organization = :organization,
283 street_address = :street_address,
284 address_level3 = :address_level3,
285 address_level2 = :address_level2,
286 address_level1 = :address_level1,
287 postal_code = :postal_code,
288 country = :country,
289 tel = :tel,
290 email = :email,
291 time_last_modified = :time_last_modified,
292 sync_change_counter = sync_change_counter + 1
293 WHERE guid = :guid",
294 rusqlite::named_params! {
295 ":name": address.name,
296 ":organization": address.organization,
297 ":street_address": address.street_address,
298 ":address_level3": address.address_level3,
299 ":address_level2": address.address_level2,
300 ":address_level1": address.address_level1,
301 ":postal_code": address.postal_code,
302 ":country": address.country,
303 ":tel": address.tel,
304 ":email": address.email,
305 ":time_last_modified": Timestamp::now(),
306 ":guid": guid,
307 },
308 )?;
309
310 tx.commit()?;
311 Ok(())
312}
313
314pub(crate) fn update_internal_address(
317 tx: &Transaction<'_>,
318 address: &InternalAddress,
319 counter: CounterUpdate,
320) -> Result<()> {
321 let (counter_sql, counter_value) = counter.as_sql();
322 let rows_changed = tx.execute(
323 &format!(
324 "UPDATE addresses_data SET
325 name = :name,
326 organization = :organization,
327 street_address = :street_address,
328 address_level3 = :address_level3,
329 address_level2 = :address_level2,
330 address_level1 = :address_level1,
331 postal_code = :postal_code,
332 country = :country,
333 tel = :tel,
334 email = :email,
335 time_created = :time_created,
336 time_last_used = :time_last_used,
337 time_last_modified = :time_last_modified,
338 times_used = :times_used,
339 sync_change_counter = {counter_sql}
340 WHERE guid = :guid"
341 ),
342 rusqlite::named_params! {
343 ":name": address.name,
344 ":organization": address.organization,
345 ":street_address": address.street_address,
346 ":address_level3": address.address_level3,
347 ":address_level2": address.address_level2,
348 ":address_level1": address.address_level1,
349 ":postal_code": address.postal_code,
350 ":country": address.country,
351 ":tel": address.tel,
352 ":email": address.email,
353 ":time_created": address.metadata.time_created,
354 ":time_last_used": address.metadata.time_last_used,
355 ":time_last_modified": address.metadata.time_last_modified,
356 ":times_used": address.metadata.times_used,
357 ":counter": counter_value,
358 ":guid": address.guid,
359 },
360 )?;
361 assert_eq!(rows_changed, 1);
364 Ok(())
365}
366
367pub(crate) fn delete_address(conn: &Connection, guid: &Guid) -> Result<bool> {
368 let tx = conn.unchecked_transaction()?;
369
370 let exists = tx.execute(
372 "DELETE FROM addresses_data
373 WHERE guid = :guid",
374 rusqlite::named_params! {
375 ":guid": guid,
376 },
377 )? != 0;
378 tx.commit()?;
379 Ok(exists)
380}
381
382pub fn touch(conn: &Connection, guid: &Guid) -> Result<()> {
383 let tx = conn.unchecked_transaction()?;
384 let now_ms = Timestamp::now();
385
386 tx.execute(
387 "UPDATE addresses_data
388 SET time_last_used = :time_last_used,
389 times_used = times_used + 1,
390 sync_change_counter = sync_change_counter + 1
391 WHERE guid = :guid",
392 rusqlite::named_params! {
393 ":time_last_used": now_ms,
394 ":guid": guid,
395 },
396 )?;
397
398 tx.commit()?;
399 Ok(())
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405 use crate::db::{schema::create_empty_sync_temp_tables, test::new_mem_db};
406 use sync_guid::Guid;
407 use types::Timestamp;
408
409 #[allow(dead_code)]
410 fn get_all(
411 conn: &Connection,
412 table_name: String,
413 ) -> rusqlite::Result<Vec<String>, rusqlite::Error> {
414 let mut stmt = conn.prepare(&format!(
415 "SELECT guid FROM {table_name}",
416 table_name = table_name
417 ))?;
418 let rows = stmt.query_map([], |row| row.get(0))?;
419
420 let mut guids = Vec::new();
421 for guid_result in rows {
422 guids.push(guid_result?);
423 }
424
425 Ok(guids)
426 }
427
428 fn insert_tombstone_record(
429 conn: &Connection,
430 guid: String,
431 ) -> rusqlite::Result<usize, rusqlite::Error> {
432 conn.execute(
433 "INSERT INTO addresses_tombstones (
434 guid,
435 time_deleted
436 ) VALUES (
437 :guid,
438 :time_deleted
439 )",
440 rusqlite::named_params! {
441 ":guid": guid,
442 ":time_deleted": Timestamp::now(),
443 },
444 )
445 }
446
447 #[test]
448 fn test_address_create_and_read() {
449 let db = new_mem_db();
450
451 let saved_address = add_address(
452 &db,
453 UpdatableAddressFields {
454 name: "jane doe".to_string(),
455 street_address: "123 Main Street".to_string(),
456 address_level2: "Seattle, WA".to_string(),
457 country: "United States".to_string(),
458
459 ..UpdatableAddressFields::default()
460 },
461 )
462 .expect("should contain saved address");
463
464 assert_ne!(Guid::default(), saved_address.guid);
466
467 assert_ne!(0, saved_address.metadata.time_created.as_millis());
469 assert_ne!(0, saved_address.metadata.time_last_modified.as_millis());
470
471 assert_eq!(0, saved_address.metadata.sync_change_counter);
472
473 let retrieved_address = get_address(&db, &saved_address.guid)
475 .expect("should contain optional retrieved address");
476 assert_eq!(saved_address.guid, retrieved_address.guid);
477 assert_eq!(saved_address.name, retrieved_address.name);
478 assert_eq!(
479 saved_address.street_address,
480 retrieved_address.street_address
481 );
482 assert_eq!(
483 saved_address.address_level2,
484 retrieved_address.address_level2
485 );
486 assert_eq!(saved_address.country, retrieved_address.country);
487
488 let delete_result = delete_address(&db, &saved_address.guid);
490 assert!(delete_result.is_ok());
491 assert!(delete_result.unwrap());
492
493 assert!(get_address(&db, &saved_address.guid).is_err());
494 }
495
496 #[test]
497 fn test_address_missing_guid() {
498 let db = new_mem_db();
499 let guid = Guid::random();
500 let result = get_address(&db, &guid);
501
502 assert_eq!(
503 result.unwrap_err().to_string(),
504 Error::NoSuchRecord(guid.to_string()).to_string()
505 );
506 }
507
508 #[test]
509 fn test_address_read_all() {
510 let db = new_mem_db();
511
512 let saved_address = add_address(
513 &db,
514 UpdatableAddressFields {
515 name: "jane doe".to_string(),
516 street_address: "123 Second Avenue".to_string(),
517 address_level2: "Chicago, IL".to_string(),
518 country: "United States".to_string(),
519
520 ..UpdatableAddressFields::default()
521 },
522 )
523 .expect("should contain saved address");
524
525 let saved_address2 = add_address(
526 &db,
527 UpdatableAddressFields {
528 name: "john deer".to_string(),
529 street_address: "123 First Avenue".to_string(),
530 address_level2: "Los Angeles, CA".to_string(),
531 country: "United States".to_string(),
532
533 ..UpdatableAddressFields::default()
534 },
535 )
536 .expect("should contain saved address");
537
538 let saved_address3 = add_address(
540 &db,
541 UpdatableAddressFields {
542 name: "abraham lincoln".to_string(),
543 street_address: "1600 Pennsylvania Ave NW".to_string(),
544 address_level2: "Washington, DC".to_string(),
545 country: "United States".to_string(),
546
547 ..UpdatableAddressFields::default()
548 },
549 )
550 .expect("should contain saved address");
551
552 let delete_result = delete_address(&db, &saved_address3.guid);
553 assert!(delete_result.is_ok());
554 assert!(delete_result.unwrap());
555
556 let retrieved_addresses =
557 get_all_addresses(&db).expect("Should contain all saved addresses");
558
559 assert!(!retrieved_addresses.is_empty());
560 let expected_number_of_addresses = 2;
561 assert_eq!(expected_number_of_addresses, retrieved_addresses.len());
562
563 let address_count = count_all_addresses(&db).expect("Should count all saved addresses");
564 assert_eq!(expected_number_of_addresses, address_count as usize);
565
566 let retrieved_address_guids = [
567 retrieved_addresses[0].guid.as_str(),
568 retrieved_addresses[1].guid.as_str(),
569 ];
570 assert!(retrieved_address_guids.contains(&saved_address.guid.as_str()));
571 assert!(retrieved_address_guids.contains(&saved_address2.guid.as_str()));
572 }
573
574 #[test]
575 fn test_address_update() {
576 let db = new_mem_db();
577
578 let saved_address = add_address(
579 &db,
580 UpdatableAddressFields {
581 name: "john doe".to_string(),
582 street_address: "1300 Broadway".to_string(),
583 address_level2: "New York, NY".to_string(),
584 country: "United States".to_string(),
585
586 ..UpdatableAddressFields::default()
587 },
588 )
589 .expect("should contain saved address");
590 assert_eq!(0, saved_address.metadata.sync_change_counter);
592
593 let expected_name = "john paul deer".to_string();
594 let update_result = update_address(
595 &db,
596 &saved_address.guid,
597 &UpdatableAddressFields {
598 name: expected_name.clone(),
599 organization: "".to_string(),
600 street_address: "123 First Avenue".to_string(),
601 address_level3: "".to_string(),
602 address_level2: "Denver, CO".to_string(),
603 address_level1: "".to_string(),
604 postal_code: "".to_string(),
605 country: "United States".to_string(),
606 tel: "".to_string(),
607 email: "".to_string(),
608 },
609 );
610 assert!(update_result.is_ok());
611
612 let updated_address =
613 get_address(&db, &saved_address.guid).expect("should contain optional updated address");
614
615 assert_eq!(saved_address.guid, updated_address.guid);
616 assert_eq!(expected_name, updated_address.name);
617
618 assert_eq!(1, updated_address.metadata.sync_change_counter);
620 }
621
622 #[test]
623 fn test_address_update_refreshes_time_last_modified() -> Result<()> {
624 let db = new_mem_db();
625
626 add_address_with_meta(&db, test_fields("123 Main Street"), test_meta("abc", 0))?;
629 assert_eq!(
630 get_address(&db, &Guid::new("abc"))?
631 .metadata
632 .time_last_modified
633 .as_millis(),
634 3000
635 );
636
637 update_address(&db, &Guid::new("abc"), &test_fields("456 Second Avenue"))?;
638
639 assert!(
642 get_address(&db, &Guid::new("abc"))?
643 .metadata
644 .time_last_modified
645 .as_millis()
646 > 3000
647 );
648
649 Ok(())
650 }
651
652 #[test]
653 fn test_address_update_internal_address() -> Result<()> {
654 let mut db = new_mem_db();
655 let tx = db.transaction()?;
656
657 let guid = Guid::random();
658 add_internal_address(
659 &tx,
660 &InternalAddress {
661 guid: guid.clone(),
662 name: "john paul deer".to_string(),
663 organization: "".to_string(),
664 street_address: "123 First Avenue".to_string(),
665 address_level3: "".to_string(),
666 address_level2: "Denver, CO".to_string(),
667 address_level1: "".to_string(),
668 postal_code: "".to_string(),
669 country: "United States".to_string(),
670 tel: "".to_string(),
671 email: "".to_string(),
672 ..Default::default()
673 },
674 )?;
675
676 let expected_name = "john paul dear";
677 update_internal_address(
678 &tx,
679 &InternalAddress {
680 guid: guid.clone(),
681 name: expected_name.to_string(),
682 organization: "".to_string(),
683 street_address: "123 First Avenue".to_string(),
684 address_level3: "".to_string(),
685 address_level2: "Denver, CO".to_string(),
686 address_level1: "".to_string(),
687 postal_code: "".to_string(),
688 country: "United States".to_string(),
689 tel: "".to_string(),
690 email: "".to_string(),
691 ..Default::default()
692 },
693 CounterUpdate::Leave,
694 )?;
695
696 let record_exists: bool = tx.query_row(
697 "SELECT EXISTS (
698 SELECT 1
699 FROM addresses_data
700 WHERE guid = :guid
701 AND name = :name
702 AND sync_change_counter = 0
703 )",
704 [&guid.to_string(), &expected_name.to_string()],
705 |row| row.get(0),
706 )?;
707 assert!(record_exists);
708
709 Ok(())
710 }
711
712 #[test]
713 fn test_address_delete() {
714 fn num_tombstones(conn: &Connection) -> u32 {
715 let stmt = "SELECT COUNT(*) from addresses_tombstones";
716 conn.query_row(stmt, [], |row| Ok(row.get::<_, u32>(0).unwrap()))
717 .unwrap()
718 }
719
720 let db = new_mem_db();
721 create_empty_sync_temp_tables(&db).expect("should create temp tables");
722
723 let saved_address = add_address(
724 &db,
725 UpdatableAddressFields {
726 name: "jane doe".to_string(),
727 street_address: "123 Second Avenue".to_string(),
728 address_level2: "Chicago, IL".to_string(),
729 country: "United States".to_string(),
730 ..UpdatableAddressFields::default()
731 },
732 )
733 .expect("first create should work");
734
735 delete_address(&db, &saved_address.guid).expect("delete should work");
736 assert_eq!(num_tombstones(&db), 0);
738
739 let saved_address = add_address(
741 &db,
742 UpdatableAddressFields {
743 name: "jane doe".to_string(),
744 street_address: "123 Second Avenue".to_string(),
745 address_level2: "Chicago, IL".to_string(),
746 country: "United States".to_string(),
747 ..UpdatableAddressFields::default()
748 },
749 )
750 .expect("create 2nd address should work");
751 db.execute(
752 &format!(
753 "INSERT INTO addresses_mirror (guid, payload) VALUES ('{}', 'whatever')",
754 saved_address.guid,
755 ),
756 [],
757 )
758 .expect("manual insert into mirror");
759 delete_address(&db, &saved_address.guid).expect("2nd delete");
760 assert_eq!(num_tombstones(&db), 1);
761 }
762
763 #[test]
764 fn test_address_trigger_on_create() {
765 let db = new_mem_db();
766 let tx = db.unchecked_transaction().expect("should get a tx");
767 let guid = Guid::random();
768
769 let tombstone_result = insert_tombstone_record(&db, guid.to_string());
771 assert!(tombstone_result.is_ok());
772
773 let address = InternalAddress {
775 guid,
776 name: "jane doe".to_string(),
777 street_address: "123 Second Avenue".to_string(),
778 address_level2: "Chicago, IL".to_string(),
779 country: "United States".to_string(),
780 ..Default::default()
781 };
782
783 let add_address_result = add_internal_address(&tx, &address);
784 assert!(add_address_result.is_err());
785
786 let expected_error_message = "guid exists in `addresses_tombstones`";
787 assert!(add_address_result
788 .unwrap_err()
789 .to_string()
790 .contains(expected_error_message))
791 }
792
793 #[test]
794 fn test_address_trigger_on_delete() {
795 let db = new_mem_db();
796 let tx = db.unchecked_transaction().expect("should get a tx");
797 let guid = Guid::random();
798
799 let address = InternalAddress {
801 guid,
802 name: "jane doe".to_string(),
803 street_address: "123 Second Avenue".to_string(),
804 address_level2: "Chicago, IL".to_string(),
805 country: "United States".to_string(),
806 ..Default::default()
807 };
808
809 let add_address_result = add_internal_address(&tx, &address);
810 assert!(add_address_result.is_ok());
811
812 let tombstone_result = insert_tombstone_record(&db, address.guid.to_string());
814 assert!(tombstone_result.is_err());
815
816 let expected_error_message = "guid exists in `addresses_data`";
817 assert_eq!(
818 expected_error_message,
819 tombstone_result.unwrap_err().to_string()
820 );
821 }
822
823 #[test]
824 fn test_address_touch() -> Result<()> {
825 let db = new_mem_db();
826 let saved_address = add_address(
827 &db,
828 UpdatableAddressFields {
829 name: "jane doe".to_string(),
830 street_address: "123 Second Avenue".to_string(),
831 address_level2: "Chicago, IL".to_string(),
832 country: "United States".to_string(),
833
834 ..UpdatableAddressFields::default()
835 },
836 )?;
837
838 assert_eq!(saved_address.metadata.sync_change_counter, 0);
839 assert_eq!(saved_address.metadata.times_used, 0);
840
841 touch(&db, &saved_address.guid)?;
842
843 let touched_address = get_address(&db, &saved_address.guid)?;
844
845 assert_eq!(touched_address.metadata.sync_change_counter, 1);
846 assert_eq!(touched_address.metadata.times_used, 1);
847
848 Ok(())
849 }
850
851 fn test_fields(street_address: &str) -> UpdatableAddressFields {
852 UpdatableAddressFields {
853 name: "jane doe".to_string(),
854 street_address: street_address.to_string(),
855 address_level2: "Seattle, WA".to_string(),
856 country: "United States".to_string(),
857 ..UpdatableAddressFields::default()
858 }
859 }
860
861 fn test_meta(guid: &str, sync_change_counter: i64) -> AddressMeta {
862 AddressMeta {
863 guid: guid.to_string(),
864 time_created: 1000,
865 time_last_used: Some(2000),
866 time_last_modified: 3000,
867 times_used: 4,
868 sync_change_counter,
869 }
870 }
871
872 #[test]
873 fn test_address_add_with_meta() -> Result<()> {
874 let db = new_mem_db();
875
876 let saved =
877 add_address_with_meta(&db, test_fields("123 Main Street"), test_meta("abc", 2))?;
878
879 assert_eq!(saved.guid.as_str(), "abc");
881
882 let retrieved = get_address(&db, &Guid::new("abc"))?;
883 assert_eq!(retrieved.street_address, "123 Main Street");
884 assert_eq!(retrieved.metadata.time_created.as_millis(), 1000);
885 assert_eq!(retrieved.metadata.time_last_used.as_millis(), 2000);
886 assert_eq!(retrieved.metadata.time_last_modified.as_millis(), 3000);
887 assert_eq!(retrieved.metadata.times_used, 4);
888 assert_eq!(retrieved.metadata.sync_change_counter, 2);
889
890 Ok(())
891 }
892
893 #[test]
894 fn test_address_add_with_meta_sanitizes_out_of_range_timestamps() -> Result<()> {
895 let db = new_mem_db();
896
897 for (guid, out_of_range) in [("abc", -1), ("def", 18446744071857664)] {
902 let meta = AddressMeta {
903 guid: guid.to_string(),
904 time_created: out_of_range,
905 time_last_used: Some(out_of_range),
906 time_last_modified: out_of_range,
907 times_used: 0,
908 sync_change_counter: 0,
909 };
910 add_address_with_meta(&db, test_fields("123 Main Street"), meta)?;
911
912 let retrieved = get_address(&db, &Guid::new(guid))?;
913 assert_eq!(
914 retrieved.metadata.time_created.as_millis(),
915 0,
916 "{out_of_range} survived"
917 );
918 assert_eq!(retrieved.metadata.time_last_used.as_millis(), 0);
919 assert_eq!(retrieved.metadata.time_last_modified.as_millis(), 0);
920 }
921
922 Ok(())
923 }
924
925 #[test]
928 fn test_address_from_row_sanitizes_corrupt_timestamps() -> Result<()> {
929 let db = new_mem_db();
930
931 let address = add_address(&db, test_fields("123 Main Street"))?;
932 db.execute(
933 "UPDATE addresses_data
936 SET time_created = 18446744071857664,
937 time_last_used = -1,
938 time_last_modified = 8640000000000001
939 WHERE guid = :guid",
940 rusqlite::named_params! { ":guid": address.guid },
941 )?;
942
943 let retrieved = get_address(&db, &address.guid)?;
944 assert_eq!(retrieved.metadata.time_created.as_millis(), 0);
945 assert_eq!(retrieved.metadata.time_last_used.as_millis(), 0);
946 assert_eq!(retrieved.metadata.time_last_modified.as_millis(), 0);
947
948 Ok(())
949 }
950
951 #[test]
952 fn test_address_update_with_meta_keeps_supplied_counter() -> Result<()> {
953 let db = new_mem_db();
954
955 add_address_with_meta(&db, test_fields("123 Main Street"), test_meta("abc", 0))?;
956
957 update_address_with_meta(&db, test_fields("456 Second Avenue"), test_meta("abc", 1))?;
959
960 let retrieved = get_address(&db, &Guid::new("abc"))?;
961 assert_eq!(retrieved.street_address, "456 Second Avenue");
962 assert_eq!(retrieved.metadata.sync_change_counter, 1);
963
964 update_address_with_meta(&db, test_fields("456 Second Avenue"), test_meta("abc", 0))?;
966 assert_eq!(
967 get_address(&db, &Guid::new("abc"))?
968 .metadata
969 .sync_change_counter,
970 0
971 );
972
973 Ok(())
974 }
975
976 #[test]
977 fn test_address_update_with_meta_errors_when_missing() -> Result<()> {
978 let db = new_mem_db();
979
980 let result =
981 update_address_with_meta(&db, test_fields("123 Main Street"), test_meta("abc", 3));
982 assert!(matches!(result, Err(Error::NoSuchRecord(guid)) if guid == "abc"));
983 assert!(get_address(&db, &Guid::new("abc")).is_err());
984
985 Ok(())
986 }
987
988 #[test]
989 fn test_address_add_many_with_meta_isolates_failures() -> Result<()> {
990 let db = new_mem_db();
991
992 let results = add_many_addresses_with_meta(
995 &db,
996 vec![
997 UpdatableAddressFieldsWithMeta {
998 fields: test_fields("1 First Street"),
999 meta: test_meta("aaa", 1),
1000 },
1001 UpdatableAddressFieldsWithMeta {
1002 fields: test_fields("2 Second Street"),
1003 meta: test_meta("", 1),
1004 },
1005 UpdatableAddressFieldsWithMeta {
1006 fields: test_fields("3 Third Street"),
1007 meta: test_meta("ccc", 1),
1008 },
1009 ],
1010 )?;
1011
1012 assert_eq!(results.len(), 3);
1013 assert!(results[0].is_ok());
1014 assert!(results[1].is_err());
1015 assert!(results[2].is_ok());
1016
1017 assert_eq!(get_all_addresses(&db)?.len(), 2);
1018 assert_eq!(get_address(&db, &Guid::new("aaa"))?.metadata.times_used, 4);
1019
1020 Ok(())
1021 }
1022
1023 #[test]
1024 fn test_delete_all_addresses_allows_a_reimport() -> Result<()> {
1025 let db = new_mem_db();
1026
1027 add_many_address_tombstones(&db, vec![("gone".to_string(), 1234)])?;
1030 let address = add_address(&db, UpdatableAddressFields::default())?;
1031
1032 delete_all_addresses(&db)?;
1033 assert_eq!(get_all_addresses(&db)?.len(), 0);
1034 let tombstones: i64 =
1035 db.query_row("SELECT COUNT(*) FROM addresses_tombstones", [], |row| {
1036 row.get(0)
1037 })?;
1038 assert_eq!(tombstones, 0, "tombstones are cleared with the records");
1039
1040 let results = add_many_addresses_with_meta(
1043 &db,
1044 vec![
1045 UpdatableAddressFieldsWithMeta {
1046 fields: UpdatableAddressFields::default(),
1047 meta: AddressMeta {
1048 guid: address.guid.to_string(),
1049 ..Default::default()
1050 },
1051 },
1052 UpdatableAddressFieldsWithMeta {
1053 fields: UpdatableAddressFields::default(),
1054 meta: AddressMeta {
1055 guid: "gone".to_string(),
1056 ..Default::default()
1057 },
1058 },
1059 ],
1060 )?;
1061 assert!(
1062 results.iter().all(|r| r.is_ok()),
1063 "a previously tombstoned guid can be re-imported: {results:?}"
1064 );
1065
1066 Ok(())
1067 }
1068
1069 #[test]
1070 fn test_address_add_many_tombstones() -> Result<()> {
1071 let db = new_mem_db();
1072
1073 let results = add_many_address_tombstones(&db, vec![("aaa".to_string(), 1234)])?;
1074 assert_eq!(results.len(), 1);
1075 assert!(results[0].is_ok());
1076
1077 let time_deleted: i64 = db.query_row(
1079 "SELECT time_deleted FROM addresses_tombstones WHERE guid = 'aaa'",
1080 [],
1081 |row| row.get(0),
1082 )?;
1083 assert_eq!(time_deleted, 1234);
1084
1085 Ok(())
1086 }
1087
1088 #[test]
1089 fn test_address_add_many_tombstones_rejects_live_guid() -> Result<()> {
1090 let db = new_mem_db();
1091
1092 add_address_with_meta(&db, test_fields("123 Main Street"), test_meta("abc", 0))?;
1093
1094 let results = add_many_address_tombstones(
1097 &db,
1098 vec![("abc".to_string(), 1234), ("ddd".to_string(), 5678)],
1099 )?;
1100
1101 assert_eq!(results.len(), 2);
1102 assert!(results[0].is_err());
1103 assert!(results[1].is_ok());
1104
1105 assert_eq!(count_tombstones(&db, "abc")?, 0);
1108 assert!(get_address(&db, &Guid::new("abc")).is_ok());
1109 assert_eq!(count_tombstones(&db, "ddd")?, 1);
1110
1111 Ok(())
1112 }
1113
1114 #[test]
1115 fn test_address_add_many_with_meta_rejects_deleted_guid() -> Result<()> {
1116 let db = new_mem_db();
1117
1118 add_many_address_tombstones(&db, vec![("aaa".to_string(), 1234)])?;
1119
1120 let results = add_many_addresses_with_meta(
1123 &db,
1124 vec![
1125 UpdatableAddressFieldsWithMeta {
1126 fields: test_fields("1 First Street"),
1127 meta: test_meta("aaa", 1),
1128 },
1129 UpdatableAddressFieldsWithMeta {
1130 fields: test_fields("2 Second Street"),
1131 meta: test_meta("bbb", 1),
1132 },
1133 ],
1134 )?;
1135
1136 assert_eq!(results.len(), 2);
1137 assert!(results[0].is_err());
1138 assert!(results[1].is_ok());
1139
1140 assert!(get_address(&db, &Guid::new("aaa")).is_err());
1141 assert_eq!(get_all_addresses(&db)?.len(), 1);
1142
1143 Ok(())
1144 }
1145
1146 fn count_tombstones(conn: &Connection, guid: &str) -> Result<i64> {
1147 Ok(conn.query_row(
1148 "SELECT COUNT(*) FROM addresses_tombstones WHERE guid = :guid",
1149 rusqlite::named_params! { ":guid": guid },
1150 |row| row.get(0),
1151 )?)
1152 }
1153}