1use 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 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
127pub(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
168pub(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; 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 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 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 assert_ne!(Guid::default(), saved_address.guid);
319
320 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 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 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 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 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 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 assert_eq!(num_tombstones(&db), 0);
561
562 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 let tombstone_result = insert_tombstone_record(&db, guid.to_string());
594 assert!(tombstone_result.is_ok());
595
596 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 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 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}