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

Repository for measurement persistence (legacy SQLite interface) More...

#include <measurement_repository.h>

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

Public Member Functions

 measurement_repository (sqlite3 *db)
 
 ~measurement_repository ()
 
 measurement_repository (const measurement_repository &)=delete
 
auto operator= (const measurement_repository &) -> measurement_repository &=delete
 
 measurement_repository (measurement_repository &&) noexcept
 
auto operator= (measurement_repository &&) noexcept -> measurement_repository &
 
auto save (const measurement_record &record) -> VoidResult
 
auto find_by_id (std::string_view measurement_id) const -> std::optional< measurement_record >
 
auto find_by_pk (int64_t pk) const -> std::optional< measurement_record >
 
auto find_by_instance (std::string_view sop_instance_uid) const -> std::vector< measurement_record >
 
auto search (const measurement_query &query) const -> std::vector< measurement_record >
 
auto remove (std::string_view measurement_id) -> VoidResult
 
auto exists (std::string_view measurement_id) const -> bool
 
auto count () const -> size_t
 
auto count (const measurement_query &query) const -> size_t
 
auto is_valid () const noexcept -> bool
 

Private Member Functions

auto parse_row (void *stmt) const -> measurement_record
 

Private Attributes

sqlite3 * db_ {nullptr}
 

Detailed Description

Repository for measurement persistence (legacy SQLite interface)

This is the legacy interface maintained for builds without database_system. New code should use the base_repository version when PACS_WITH_DATABASE_SYSTEM is defined.

Definition at line 166 of file measurement_repository.h.

Constructor & Destructor Documentation

◆ measurement_repository() [1/3]

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

◆ ~measurement_repository()

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

◆ measurement_repository() [2/3]

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

◆ measurement_repository() [3/3]

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

Member Function Documentation

◆ count() [1/2]

size_t kcenon::pacs::storage::measurement_repository::count ( ) const -> size_t
nodiscard

Definition at line 650 of file measurement_repository.cpp.

654 {
655 if (!db_) return 0;
656
657 static constexpr const char* sql = "SELECT COUNT(*) FROM measurements";
658
659 sqlite3_stmt* stmt = nullptr;
660 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
661 return 0;
662 }
663
664 size_t result = 0;
665 if (sqlite3_step(stmt) == SQLITE_ROW) {
666 result = static_cast<size_t>(sqlite3_column_int64(stmt, 0));
667 }

Referenced by kcenon::pacs::web::endpoints::register_measurement_endpoints_impl().

Here is the caller graph for this function:

◆ count() [2/2]

size_t kcenon::pacs::storage::measurement_repository::count ( const measurement_query & query) const -> size_t
nodiscard

Definition at line 669 of file measurement_repository.cpp.

673 {
674 if (!db_) return 0;
675
676 std::ostringstream sql;
677 sql << "SELECT COUNT(*) FROM measurements WHERE 1=1";
678
679 std::vector<std::pair<int, std::string>> bindings;
680 int param_idx = 1;
681
682 if (query.sop_instance_uid.has_value()) {
683 sql << " AND sop_instance_uid = ?";
684 bindings.emplace_back(param_idx++, query.sop_instance_uid.value());
685 }
686
687 if (query.user_id.has_value()) {
688 sql << " AND user_id = ?";
689 bindings.emplace_back(param_idx++, query.user_id.value());
690 }
691
692 if (query.type.has_value()) {
693 sql << " AND measurement_type = ?";
694 bindings.emplace_back(param_idx++, to_string(query.type.value()));
695 }
696
697 sqlite3_stmt* stmt = nullptr;
698 auto sql_str = sql.str();
699 if (sqlite3_prepare_v2(db_, sql_str.c_str(), -1, &stmt, nullptr) !=
700 SQLITE_OK) {
701 return 0;
702 }
703
704 for (const auto& [idx, value] : bindings) {
705 sqlite3_bind_text(stmt, idx, value.c_str(), -1, SQLITE_TRANSIENT);
706 }
707
708 size_t result = 0;
709 if (sqlite3_step(stmt) == SQLITE_ROW) {
710 result = static_cast<size_t>(sqlite3_column_int64(stmt, 0));
711 }
const atna_coded_value query
Query (110112)
auto to_string(annotation_type type) -> std::string
Convert annotation_type to string.

◆ exists()

bool kcenon::pacs::storage::measurement_repository::exists ( std::string_view measurement_id) const -> bool
nodiscard

Definition at line 631 of file measurement_repository.cpp.

635 {
636 if (!db_) return false;
637
638 static constexpr const char* sql =
639 "SELECT 1 FROM measurements WHERE measurement_id = ?";
640
641 sqlite3_stmt* stmt = nullptr;
642 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
643 return false;
644 }
645
646 sqlite3_bind_text(stmt, 1, measurement_id.data(),
647 static_cast<int>(measurement_id.size()), SQLITE_TRANSIENT);
648

Referenced by kcenon::pacs::web::endpoints::register_measurement_endpoints_impl().

Here is the caller graph for this function:

◆ find_by_id()

std::optional< measurement_record > kcenon::pacs::storage::measurement_repository::find_by_id ( std::string_view measurement_id) const -> std::optional<measurement_record>
nodiscard

Definition at line 487 of file measurement_repository.cpp.

488 {
489 if (!db_) return std::nullopt;
490
491 static constexpr const char* sql = R"(
492 SELECT pk, measurement_id, sop_instance_uid, frame_number, user_id,
493 measurement_type, geometry_json, value, unit, label, created_at
494 FROM measurements WHERE measurement_id = ?
495 )";
496
497 sqlite3_stmt* stmt = nullptr;
498 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
499 return std::nullopt;
500 }
501
502 sqlite3_bind_text(stmt, 1, measurement_id.data(),
503 static_cast<int>(measurement_id.size()), SQLITE_TRANSIENT);
504
505 std::optional<measurement_record> result;
506 if (sqlite3_step(stmt) == SQLITE_ROW) {
507 result = parse_row(stmt);
508 }
509
510 sqlite3_finalize(stmt);
511 return result;
512}
auto parse_row(void *stmt) const -> measurement_record

References db_, and parse_row().

Referenced by kcenon::pacs::web::endpoints::register_measurement_endpoints_impl().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ find_by_instance()

std::vector< measurement_record > kcenon::pacs::storage::measurement_repository::find_by_instance ( std::string_view sop_instance_uid) const -> std::vector<measurement_record>
nodiscard

Definition at line 540 of file measurement_repository.cpp.

541 {
542 measurement_query query;
543 query.sop_instance_uid = std::string(sop_instance_uid);
544 return search(query);
545}
auto search(const measurement_query &query) const -> std::vector< measurement_record >

References search().

Referenced by kcenon::pacs::web::endpoints::register_measurement_endpoints_impl().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ find_by_pk()

std::optional< measurement_record > kcenon::pacs::storage::measurement_repository::find_by_pk ( int64_t pk) const -> std::optional<measurement_record>
nodiscard

Definition at line 514 of file measurement_repository.cpp.

515 {
516 if (!db_) return std::nullopt;
517
518 static constexpr const char* sql = R"(
519 SELECT pk, measurement_id, sop_instance_uid, frame_number, user_id,
520 measurement_type, geometry_json, value, unit, label, created_at
521 FROM measurements WHERE pk = ?
522 )";
523
524 sqlite3_stmt* stmt = nullptr;
525 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
526 return std::nullopt;
527 }
528
529 sqlite3_bind_int64(stmt, 1, pk);
530
531 std::optional<measurement_record> result;
532 if (sqlite3_step(stmt) == SQLITE_ROW) {
533 result = parse_row(stmt);
534 }
535
536 sqlite3_finalize(stmt);
537 return result;
538}

References db_, and parse_row().

Here is the call graph for this function:

◆ is_valid()

bool kcenon::pacs::storage::measurement_repository::is_valid ( ) const -> bool
nodiscardnoexcept

Definition at line 713 of file measurement_repository.cpp.

◆ operator=() [1/2]

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

◆ operator=() [2/2]

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

◆ parse_row()

measurement_record kcenon::pacs::storage::measurement_repository::parse_row ( void * stmt) const -> measurement_record
nodiscardprivate

Definition at line 717 of file measurement_repository.cpp.

717 {
718 return db_ != nullptr;
719}
720
721measurement_record measurement_repository::parse_row(void* stmt_ptr) const {
722 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
723 measurement_record record;
724
725 int col = 0;
726 record.pk = get_int64_column(stmt, col++);
727 record.measurement_id = get_text_column(stmt, col++);
728 record.sop_instance_uid = get_text_column(stmt, col++);
729 record.frame_number = get_optional_int(stmt, col++);
730 record.user_id = get_text_column(stmt, col++);
731
732 auto type_str = get_text_column(stmt, col++);
733 record.type =
735
736 record.geometry_json = get_text_column(stmt, col++);
737 record.value = get_double_column(stmt, col++);
738 record.unit = get_text_column(stmt, col++);
739 record.label = get_text_column(stmt, col++);
740
741 auto created_str = get_text_column(stmt, col++);
auto measurement_type_from_string(std::string_view str) -> std::optional< measurement_type >
Parse string to measurement_type.
@ length
Linear distance measurement.

References db_.

Referenced by find_by_id(), find_by_pk(), and search().

Here is the caller graph for this function:

◆ remove()

VoidResult kcenon::pacs::storage::measurement_repository::remove ( std::string_view measurement_id) -> VoidResult
nodiscard

Definition at line 598 of file measurement_repository.cpp.

602 {
603 if (!db_) {
604 return VoidResult(kcenon::common::error_info{
605 -1, "Database not initialized", "measurement_repository"});
606 }
607
608 static constexpr const char* sql =
609 "DELETE FROM measurements WHERE measurement_id = ?";
610
611 sqlite3_stmt* stmt = nullptr;
612 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
613 return VoidResult(kcenon::common::error_info{
614 -1,
615 "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
616 "measurement_repository"});
617 }
618
619 sqlite3_bind_text(stmt, 1, measurement_id.data(),
620 static_cast<int>(measurement_id.size()), SQLITE_TRANSIENT);
621
622 auto rc = sqlite3_step(stmt);
623 sqlite3_finalize(stmt);
624
625 if (rc != SQLITE_DONE) {
626 return VoidResult(kcenon::common::error_info{
627 -1,
628 "Failed to delete measurement: " + std::string(sqlite3_errmsg(db_)),
629 "measurement_repository"});

Referenced by kcenon::pacs::web::endpoints::register_measurement_endpoints_impl().

Here is the caller graph for this function:

◆ save()

VoidResult kcenon::pacs::storage::measurement_repository::save ( const measurement_record & record) -> VoidResult
nodiscard

Definition at line 430 of file measurement_repository.cpp.

430 {
431 if (!db_) {
432 return VoidResult(kcenon::common::error_info{
433 -1, "Database not initialized", "measurement_repository"});
434 }
435
436 static constexpr const char* sql = R"(
437 INSERT INTO measurements (
438 measurement_id, sop_instance_uid, frame_number, user_id,
439 measurement_type, geometry_json, value, unit, label, created_at
440 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
441 ON CONFLICT(measurement_id) DO UPDATE SET
442 geometry_json = excluded.geometry_json,
443 value = excluded.value,
444 unit = excluded.unit,
445 label = excluded.label
446 )";
447
448 sqlite3_stmt* stmt = nullptr;
449 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
450 return VoidResult(kcenon::common::error_info{
451 -1,
452 "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
453 "measurement_repository"});
454 }
455
456 auto now_str = to_timestamp_string(std::chrono::system_clock::now());
457
458 int idx = 1;
459 sqlite3_bind_text(stmt, idx++, record.measurement_id.c_str(), -1,
460 SQLITE_TRANSIENT);
461 sqlite3_bind_text(stmt, idx++, record.sop_instance_uid.c_str(), -1,
462 SQLITE_TRANSIENT);
463 bind_optional_int(stmt, idx++, record.frame_number);
464 sqlite3_bind_text(stmt, idx++, record.user_id.c_str(), -1, SQLITE_TRANSIENT);
465 sqlite3_bind_text(stmt, idx++, to_string(record.type).c_str(), -1,
466 SQLITE_TRANSIENT);
467 sqlite3_bind_text(stmt, idx++, record.geometry_json.c_str(), -1,
468 SQLITE_TRANSIENT);
469 sqlite3_bind_double(stmt, idx++, record.value);
470 sqlite3_bind_text(stmt, idx++, record.unit.c_str(), -1, SQLITE_TRANSIENT);
471 sqlite3_bind_text(stmt, idx++, record.label.c_str(), -1, SQLITE_TRANSIENT);
472 sqlite3_bind_text(stmt, idx++, now_str.c_str(), -1, SQLITE_TRANSIENT);
473
474 auto rc = sqlite3_step(stmt);
475 sqlite3_finalize(stmt);
476
477 if (rc != SQLITE_DONE) {
478 return VoidResult(kcenon::common::error_info{
479 -1,
480 "Failed to save measurement: " + std::string(sqlite3_errmsg(db_)),
481 "measurement_repository"});
482 }
483
484 return kcenon::common::ok();
485}

References kcenon::pacs::storage::to_string().

Referenced by kcenon::pacs::web::endpoints::register_measurement_endpoints_impl().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ search()

std::vector< measurement_record > kcenon::pacs::storage::measurement_repository::search ( const measurement_query & query) const -> std::vector<measurement_record>
nodiscard

Definition at line 547 of file measurement_repository.cpp.

548 {
549 std::vector<measurement_record> result;
550 if (!db_) return result;
551
552 std::ostringstream sql;
553 sql << R"(
554 SELECT pk, measurement_id, sop_instance_uid, frame_number, user_id,
555 measurement_type, geometry_json, value, unit, label, created_at
556 FROM measurements WHERE 1=1
557 )";
558
559 std::vector<std::pair<int, std::string>> bindings;
560 int param_idx = 1;
561
562 if (query.sop_instance_uid.has_value()) {
563 sql << " AND sop_instance_uid = ?";
564 bindings.emplace_back(param_idx++, query.sop_instance_uid.value());
565 }
566
567 if (query.user_id.has_value()) {
568 sql << " AND user_id = ?";
569 bindings.emplace_back(param_idx++, query.user_id.value());
570 }
571
572 if (query.type.has_value()) {
573 sql << " AND measurement_type = ?";
574 bindings.emplace_back(param_idx++, to_string(query.type.value()));
575 }
576
577 sql << " ORDER BY created_at DESC";
578
579 if (query.limit > 0) {
580 sql << " LIMIT " << query.limit << " OFFSET " << query.offset;
581 }
582
583 sqlite3_stmt* stmt = nullptr;
584 auto sql_str = sql.str();
585 if (sqlite3_prepare_v2(db_, sql_str.c_str(), -1, &stmt, nullptr) !=
586 SQLITE_OK) {
587 return result;
588 }
589
590 for (const auto& [idx, value] : bindings) {
591 sqlite3_bind_text(stmt, idx, value.c_str(), -1, SQLITE_TRANSIENT);
592 }
593
594 while (sqlite3_step(stmt) == SQLITE_ROW) {
595 result.push_back(parse_row(stmt));
596 }

References db_, parse_row(), and kcenon::pacs::storage::to_string().

Referenced by find_by_instance(), and kcenon::pacs::web::endpoints::register_measurement_endpoints_impl().

Here is the call graph for this function:
Here is the caller graph for this function:

Member Data Documentation

◆ db_

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

Definition at line 196 of file measurement_repository.h.

196{nullptr};

Referenced by find_by_id(), find_by_pk(), parse_row(), and search().


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