PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
kcenon::pacs::storage::patient_repository Class Reference

Repository for patient metadata persistence (legacy SQLite interface) More...

#include <patient_repository.h>

Collaboration diagram for kcenon::pacs::storage::patient_repository:
Collaboration graph

Public Member Functions

 patient_repository (sqlite3 *db)
 
 ~patient_repository ()
 
 patient_repository (const patient_repository &)=delete
 
auto operator= (const patient_repository &) -> patient_repository &=delete
 
 patient_repository (patient_repository &&) noexcept
 
auto operator= (patient_repository &&) noexcept -> patient_repository &
 
auto upsert_patient (std::string_view patient_id, std::string_view patient_name="", std::string_view birth_date="", std::string_view sex="") -> Result< int64_t >
 
auto upsert_patient (const patient_record &record) -> Result< int64_t >
 
auto find_patient (std::string_view patient_id) const -> std::optional< patient_record >
 
auto find_patient_by_pk (int64_t pk) const -> std::optional< patient_record >
 
auto search_patients (const patient_query &query) const -> Result< std::vector< patient_record > >
 
auto delete_patient (std::string_view patient_id) -> VoidResult
 
auto patient_count () const -> Result< size_t >
 

Private Member Functions

auto parse_patient_row (void *stmt) const -> patient_record
 

Static Private Member Functions

static auto to_like_pattern (std::string_view pattern) -> std::string
 

Private Attributes

sqlite3 * db_ {nullptr}
 

Detailed Description

Repository for patient metadata persistence (legacy SQLite interface)

Definition at line 179 of file patient_repository.h.

Constructor & Destructor Documentation

◆ patient_repository() [1/3]

kcenon::pacs::storage::patient_repository::patient_repository ( sqlite3 * db)
explicit

Definition at line 524 of file patient_repository.cpp.

◆ ~patient_repository()

kcenon::pacs::storage::patient_repository::~patient_repository ( )
default

◆ patient_repository() [2/3]

kcenon::pacs::storage::patient_repository::patient_repository ( const patient_repository & )
delete

◆ patient_repository() [3/3]

kcenon::pacs::storage::patient_repository::patient_repository ( patient_repository && )
defaultnoexcept

Member Function Documentation

◆ delete_patient()

auto kcenon::pacs::storage::patient_repository::delete_patient ( std::string_view patient_id) -> VoidResult
nodiscard

Definition at line 790 of file patient_repository.cpp.

791 {
792 const char* sql = "DELETE FROM patients WHERE patient_id = ?;";
793
794 sqlite3_stmt* stmt = nullptr;
795 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
796 if (rc != SQLITE_OK) {
797 return make_error<std::monostate>(
798 rc,
799 kcenon::pacs::compat::format("Failed to prepare delete: {}", sqlite3_errmsg(db_)),
800 "storage");
801 }
802
803 sqlite3_bind_text(stmt, 1, patient_id.data(),
804 static_cast<int>(patient_id.size()), SQLITE_TRANSIENT);
805
806 rc = sqlite3_step(stmt);
807 sqlite3_finalize(stmt);
808
809 if (rc != SQLITE_DONE) {
810 return make_error<std::monostate>(
811 rc, kcenon::pacs::compat::format("Failed to delete patient: {}", sqlite3_errmsg(db_)),
812 "storage");
813 }
814
815 return ok();
816}
constexpr dicom_tag patient_id
Patient ID.

◆ find_patient()

auto kcenon::pacs::storage::patient_repository::find_patient ( std::string_view patient_id) const -> std::optional<patient_record>
nodiscard

Definition at line 656 of file patient_repository.cpp.

657 {
658 const char* sql = R"(
659 SELECT patient_pk, patient_id, patient_name, birth_date, sex,
660 other_ids, ethnic_group, comments, created_at, updated_at
661 FROM patients
662 WHERE patient_id = ?;
663 )";
664
665 sqlite3_stmt* stmt = nullptr;
666 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
667 if (rc != SQLITE_OK) {
668 return std::nullopt;
669 }
670
671 sqlite3_bind_text(stmt, 1, patient_id.data(),
672 static_cast<int>(patient_id.size()), SQLITE_TRANSIENT);
673
674 rc = sqlite3_step(stmt);
675 if (rc != SQLITE_ROW) {
676 sqlite3_finalize(stmt);
677 return std::nullopt;
678 }
679
680 auto record = parse_patient_row(stmt);
681 sqlite3_finalize(stmt);
682
683 return record;
684}
auto parse_patient_row(void *stmt) const -> patient_record

◆ find_patient_by_pk()

auto kcenon::pacs::storage::patient_repository::find_patient_by_pk ( int64_t pk) const -> std::optional<patient_record>
nodiscard

Definition at line 686 of file patient_repository.cpp.

687 {
688 const char* sql = R"(
689 SELECT patient_pk, patient_id, patient_name, birth_date, sex,
690 other_ids, ethnic_group, comments, created_at, updated_at
691 FROM patients
692 WHERE patient_pk = ?;
693 )";
694
695 sqlite3_stmt* stmt = nullptr;
696 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
697 if (rc != SQLITE_OK) {
698 return std::nullopt;
699 }
700
701 sqlite3_bind_int64(stmt, 1, pk);
702
703 rc = sqlite3_step(stmt);
704 if (rc != SQLITE_ROW) {
705 sqlite3_finalize(stmt);
706 return std::nullopt;
707 }
708
709 auto record = parse_patient_row(stmt);
710 sqlite3_finalize(stmt);
711
712 return record;
713}

◆ operator=() [1/2]

auto kcenon::pacs::storage::patient_repository::operator= ( const patient_repository & ) -> patient_repository &=delete
delete

◆ operator=() [2/2]

auto kcenon::pacs::storage::patient_repository::operator= ( patient_repository && ) -> patient_repository &
defaultnoexcept

◆ parse_patient_row()

auto kcenon::pacs::storage::patient_repository::parse_patient_row ( void * stmt) const -> patient_record
nodiscardprivate

Definition at line 554 of file patient_repository.cpp.

555 {
556 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
557 patient_record record;
558
559 record.pk = sqlite3_column_int64(stmt, 0);
560 record.patient_id = get_text(stmt, 1);
561 record.patient_name = get_text(stmt, 2);
562 record.birth_date = get_text(stmt, 3);
563 record.sex = get_text(stmt, 4);
564 record.other_ids = get_text(stmt, 5);
565 record.ethnic_group = get_text(stmt, 6);
566 record.comments = get_text(stmt, 7);
567
568 auto created_str = get_text(stmt, 8);
569 record.created_at = parse_datetime(created_str.c_str());
570
571 auto updated_str = get_text(stmt, 9);
572 record.updated_at = parse_datetime(updated_str.c_str());
573
574 return record;
575}

◆ patient_count()

auto kcenon::pacs::storage::patient_repository::patient_count ( ) const -> Result<size_t>
nodiscard

Definition at line 818 of file patient_repository.cpp.

818 {
819 const char* sql = "SELECT COUNT(*) FROM patients;";
820
821 sqlite3_stmt* stmt = nullptr;
822 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
823 if (rc != SQLITE_OK) {
824 return make_error<size_t>(
826 kcenon::pacs::compat::format("Failed to prepare query: {}", sqlite3_errmsg(db_)),
827 "storage");
828 }
829
830 size_t count = 0;
831 if (sqlite3_step(stmt) == SQLITE_ROW) {
832 count = static_cast<size_t>(sqlite3_column_int64(stmt, 0));
833 }
834
835 sqlite3_finalize(stmt);
836 return ok(count);
837}
constexpr int database_query_error
Definition result.h:122

References kcenon::pacs::error_codes::database_query_error, and db_.

◆ search_patients()

auto kcenon::pacs::storage::patient_repository::search_patients ( const patient_query & query) const -> Result<std::vector<patient_record>>
nodiscard

Definition at line 715 of file patient_repository.cpp.

716 {
717 std::vector<patient_record> results;
718
719 std::string sql = R"(
720 SELECT patient_pk, patient_id, patient_name, birth_date, sex,
721 other_ids, ethnic_group, comments, created_at, updated_at
722 FROM patients
723 WHERE 1=1
724 )";
725
726 std::vector<std::string> params;
727
728 if (query.patient_id.has_value()) {
729 sql += " AND patient_id LIKE ?";
730 params.push_back(to_like_pattern(*query.patient_id));
731 }
732
733 if (query.patient_name.has_value()) {
734 sql += " AND patient_name LIKE ?";
735 params.push_back(to_like_pattern(*query.patient_name));
736 }
737
738 if (query.birth_date.has_value()) {
739 sql += " AND birth_date = ?";
740 params.push_back(*query.birth_date);
741 }
742
743 if (query.birth_date_from.has_value()) {
744 sql += " AND birth_date >= ?";
745 params.push_back(*query.birth_date_from);
746 }
747
748 if (query.birth_date_to.has_value()) {
749 sql += " AND birth_date <= ?";
750 params.push_back(*query.birth_date_to);
751 }
752
753 if (query.sex.has_value()) {
754 sql += " AND sex = ?";
755 params.push_back(*query.sex);
756 }
757
758 sql += " ORDER BY patient_name, patient_id";
759
760 if (query.limit > 0) {
761 sql += kcenon::pacs::compat::format(" LIMIT {}", query.limit);
762 }
763
764 if (query.offset > 0) {
765 sql += kcenon::pacs::compat::format(" OFFSET {}", query.offset);
766 }
767
768 sqlite3_stmt* stmt = nullptr;
769 auto rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr);
770 if (rc != SQLITE_OK) {
771 return make_error<std::vector<patient_record>>(
773 kcenon::pacs::compat::format("Failed to prepare query: {}", sqlite3_errmsg(db_)),
774 "storage");
775 }
776
777 for (size_t i = 0; i < params.size(); ++i) {
778 sqlite3_bind_text(stmt, static_cast<int>(i + 1), params[i].c_str(), -1,
779 SQLITE_TRANSIENT);
780 }
781
782 while (sqlite3_step(stmt) == SQLITE_ROW) {
783 results.push_back(parse_patient_row(stmt));
784 }
785
786 sqlite3_finalize(stmt);
787 return ok(std::move(results));
788}
static auto to_like_pattern(std::string_view pattern) -> std::string
const atna_coded_value query
Query (110112)

References kcenon::pacs::error_codes::database_query_error.

◆ to_like_pattern()

auto kcenon::pacs::storage::patient_repository::to_like_pattern ( std::string_view pattern) -> std::string
staticnodiscardprivate

Definition at line 533 of file patient_repository.cpp.

534 {
535 std::string result;
536 result.reserve(pattern.size());
537
538 for (char c : pattern) {
539 if (c == '*') {
540 result += '%';
541 } else if (c == '?') {
542 result += '_';
543 } else if (c == '%' || c == '_') {
544 result += '\\';
545 result += c;
546 } else {
547 result += c;
548 }
549 }
550
551 return result;
552}

◆ upsert_patient() [1/2]

auto kcenon::pacs::storage::patient_repository::upsert_patient ( const patient_record & record) -> Result<int64_t>
nodiscard

Definition at line 590 of file patient_repository.cpp.

591 {
592 if (record.patient_id.empty()) {
593 return make_error<int64_t>(-1, "Patient ID is required", "storage");
594 }
595
596 if (record.patient_id.length() > 64) {
597 return make_error<int64_t>(
598 -1, "Patient ID exceeds maximum length of 64 characters",
599 "storage");
600 }
601
602 if (!record.sex.empty() && record.sex != "M" && record.sex != "F" &&
603 record.sex != "O") {
604 return make_error<int64_t>(
605 -1, "Invalid sex value. Must be M, F, or O", "storage");
606 }
607
608 const char* sql = R"(
609 INSERT INTO patients (
610 patient_id, patient_name, birth_date, sex,
611 other_ids, ethnic_group, comments, updated_at
612 ) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
613 ON CONFLICT(patient_id) DO UPDATE SET
614 patient_name = excluded.patient_name,
615 birth_date = excluded.birth_date,
616 sex = excluded.sex,
617 other_ids = excluded.other_ids,
618 ethnic_group = excluded.ethnic_group,
619 comments = excluded.comments,
620 updated_at = datetime('now')
621 RETURNING patient_pk;
622 )";
623
624 sqlite3_stmt* stmt = nullptr;
625 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
626 if (rc != SQLITE_OK) {
627 return make_error<int64_t>(
628 rc,
629 kcenon::pacs::compat::format("Failed to prepare statement: {}", sqlite3_errmsg(db_)),
630 "storage");
631 }
632
633 sqlite3_bind_text(stmt, 1, record.patient_id.c_str(), -1, SQLITE_TRANSIENT);
634 sqlite3_bind_text(stmt, 2, record.patient_name.c_str(), -1, SQLITE_TRANSIENT);
635 sqlite3_bind_text(stmt, 3, record.birth_date.c_str(), -1, SQLITE_TRANSIENT);
636 sqlite3_bind_text(stmt, 4, record.sex.c_str(), -1, SQLITE_TRANSIENT);
637 sqlite3_bind_text(stmt, 5, record.other_ids.c_str(), -1, SQLITE_TRANSIENT);
638 sqlite3_bind_text(stmt, 6, record.ethnic_group.c_str(), -1, SQLITE_TRANSIENT);
639 sqlite3_bind_text(stmt, 7, record.comments.c_str(), -1, SQLITE_TRANSIENT);
640
641 rc = sqlite3_step(stmt);
642 if (rc != SQLITE_ROW) {
643 auto error_msg = sqlite3_errmsg(db_);
644 sqlite3_finalize(stmt);
645 return make_error<int64_t>(
646 rc, kcenon::pacs::compat::format("Failed to upsert patient: {}", error_msg),
647 "storage");
648 }
649
650 auto pk = sqlite3_column_int64(stmt, 0);
651 sqlite3_finalize(stmt);
652
653 return pk;
654}

◆ upsert_patient() [2/2]

auto kcenon::pacs::storage::patient_repository::upsert_patient ( std::string_view patient_id,
std::string_view patient_name = "",
std::string_view birth_date = "",
std::string_view sex = "" ) -> Result<int64_t>
nodiscard

Definition at line 577 of file patient_repository.cpp.

581 {
582 patient_record record;
583 record.patient_id = std::string(patient_id);
584 record.patient_name = std::string(patient_name);
585 record.birth_date = std::string(birth_date);
586 record.sex = std::string(sex);
587 return upsert_patient(record);
588}
auto upsert_patient(std::string_view patient_id, std::string_view patient_name="", std::string_view birth_date="", std::string_view sex="") -> Result< int64_t >

Member Data Documentation

◆ db_

sqlite3* kcenon::pacs::storage::patient_repository::db_ {nullptr}
private

Definition at line 211 of file patient_repository.h.

211{nullptr};

Referenced by patient_count().


The documentation for this class was generated from the following files: