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 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
159pub(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; 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 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 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 assert_ne!(Guid::default(), saved_address.guid);
310
311 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 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 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 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 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 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 assert_eq!(num_tombstones(&db), 0);
549
550 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 let tombstone_result = insert_tombstone_record(&db, guid.to_string());
582 assert!(tombstone_result.is_ok());
583
584 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 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 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}