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::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 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
53pub(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
69pub(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
89fn 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
116pub(crate) fn delete_all_addresses(conn: &Connection) -> Result<()> {
123 let tx = conn.unchecked_transaction()?;
124 tx.execute("DELETE FROM addresses_data", [])?;
125 tx.execute("DELETE FROM addresses_tombstones", [])?;
127 tx.commit()?;
128 Ok(())
129}
130
131pub(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
163fn 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
197pub(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 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
305pub(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
346pub(crate) enum CounterUpdate {
348 Increment,
350 Leave,
353 Set(i64),
355}
356
357impl CounterUpdate {
358 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
371pub(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 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 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 assert_ne!(Guid::default(), saved_address.guid);
523
524 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 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 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 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 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 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 assert_eq!(num_tombstones(&db), 0);
765
766 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 let tombstone_result = insert_tombstone_record(&db, guid.to_string());
798 assert!(tombstone_result.is_ok());
799
800 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 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 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 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 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 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 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 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 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 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 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 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 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}