27#ifdef PACS_WITH_DATABASE_SYSTEM
28#include <database/query_builder.h>
45using kcenon::common::make_error;
46using kcenon::common::ok;
60auto parse_datetime(
const char* str)
61 -> std::chrono::system_clock::time_point {
62 if (!str || *str ==
'\0') {
63 return std::chrono::system_clock::now();
67 std::istringstream ss(str);
68 ss >> std::get_time(&tm,
"%Y-%m-%d %H:%M:%S");
71 return std::chrono::system_clock::now();
74 return std::chrono::system_clock::from_time_t(std::mktime(&tm));
80auto get_text(sqlite3_stmt* stmt,
int col) -> std::string {
82 reinterpret_cast<const char*
>(sqlite3_column_text(stmt, col));
83 return text ? std::string(
text) : std::string{};
86#ifdef PACS_WITH_DATABASE_SYSTEM
91 const std::map<std::string, database::core::database_value>& row,
92 const std::string& key) -> std::string {
93 auto it = row.find(key);
94 if (it == row.end()) {
97 if (std::holds_alternative<std::string>(it->second)) {
98 return std::get<std::string>(it->second);
107 const std::map<std::string, database::core::database_value>& row,
108 const std::string& key) -> int64_t {
109 auto it = row.find(key);
110 if (it == row.end()) {
113 if (std::holds_alternative<int64_t>(it->second)) {
114 return std::get<int64_t>(it->second);
116 if (std::holds_alternative<std::string>(it->second)) {
118 return std::stoll(std::get<std::string>(it->second));
126auto create_adapter_compatible_memory_path() -> std::string {
127 static std::atomic<uint64_t>
counter{0};
129 const auto unique_id =
130 std::chrono::steady_clock::now().time_since_epoch().count() +
131 static_cast<std::chrono::steady_clock::rep
>(
counter.fetch_add(1));
133 const auto file_name =
134 kcenon::pacs::compat::format(
"pacs_index_memory_{}.sqlite", unique_id);
135 return (std::filesystem::temp_directory_path() / file_name).string();
139void remove_database_sidecars(
const std::string& path) {
145 std::filesystem::remove(path, ec);
146 std::filesystem::remove(path +
"-wal", ec);
147 std::filesystem::remove(path +
"-shm", ec);
163 sqlite3* db =
nullptr;
165 std::string effective_path;
166#ifdef PACS_WITH_DATABASE_SYSTEM
167 bool remove_on_close =
false;
168 if (db_path ==
":memory:") {
171 effective_path = create_adapter_compatible_memory_path();
172 remove_on_close =
true;
174 effective_path = std::string(db_path);
177 effective_path = std::string(db_path);
180 auto rc = sqlite3_open_v2(effective_path.c_str(), &db,
181 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI,
183 if (rc != SQLITE_OK) {
184 std::string error_msg =
185 db ? sqlite3_errmsg(db) :
"Failed to allocate memory";
189 return make_error<std::unique_ptr<index_database>>(
190 rc, kcenon::pacs::compat::format(
"Failed to open database: {}", error_msg),
195 rc = sqlite3_exec(db,
"PRAGMA foreign_keys = ON;",
nullptr,
nullptr,
197 if (rc != SQLITE_OK) {
199 return make_error<std::unique_ptr<index_database>>(
200 rc,
"Failed to enable foreign keys",
"storage");
204 if (config.wal_mode && db_path !=
":memory:") {
205 rc = sqlite3_exec(db,
"PRAGMA journal_mode = WAL;",
nullptr,
nullptr,
207 if (rc != SQLITE_OK) {
209 return make_error<std::unique_ptr<index_database>>(
210 rc,
"Failed to enable WAL mode",
"storage");
216 kcenon::pacs::compat::format(
"PRAGMA cache_size = -{};", config.cache_size_mb * 1024);
217 rc = sqlite3_exec(db, cache_sql.c_str(),
nullptr,
nullptr,
nullptr);
218 if (rc != SQLITE_OK) {
220 return make_error<std::unique_ptr<index_database>>(
221 rc,
"Failed to set cache size",
"storage");
225 if (config.mmap_enabled && db_path !=
":memory:") {
226 auto mmap_sql = kcenon::pacs::compat::format(
"PRAGMA mmap_size = {};", config.mmap_size);
227 rc = sqlite3_exec(db, mmap_sql.c_str(),
nullptr,
nullptr,
nullptr);
228 if (rc != SQLITE_OK) {
234 rc = sqlite3_exec(db,
"PRAGMA synchronous = NORMAL;",
nullptr,
nullptr,
236 if (rc != SQLITE_OK) {
241 auto instance = std::unique_ptr<index_database>(
243#ifdef PACS_WITH_DATABASE_SYSTEM
244 instance->remove_on_close_ = remove_on_close;
250 return make_error<std::unique_ptr<index_database>>(
252 kcenon::pacs::compat::format(
"Migration failed: {}",
257#ifdef PACS_WITH_DATABASE_SYSTEM
260 auto db_init_result = instance->initialize_database_system();
261 if (db_init_result.is_err()) {
267 auto repository_result = instance->initialize_repositories();
268 if (repository_result.is_err()) {
269 return make_error<std::unique_ptr<index_database>>(
270 repository_result.error().
code,
271 kcenon::pacs::compat::format(
"Repository initialization failed: {}",
272 repository_result.error().message),
280 : db_(db), path_(std::move(path)) {}
291#ifdef PACS_WITH_DATABASE_SYSTEM
293 (void)db_adapter_->disconnect();
302 remove_database_sidecars(
path_);
306#ifdef PACS_WITH_DATABASE_SYSTEM
307auto index_database::initialize_database_system() -> VoidResult {
308 return initialize_database_adapter();
311auto index_database::initialize_database_adapter() -> VoidResult {
312 db_adapter_ = std::make_shared<pacs_database_adapter>(
313 std::filesystem::path(path_));
315 auto connect_result = db_adapter_->connect();
316 if (connect_result.is_err()) {
318 return make_error<std::monostate>(
320 kcenon::pacs::compat::format(
"Failed to connect adapter: {}",
321 connect_result.error().message),
328auto index_database::parse_patient_from_adapter_row(
const database_row& row)
const
330 patient_record record;
332 auto get_str = [&row](
const std::string& key) -> std::string {
333 auto it = row.find(key);
334 return (it != row.end()) ? it->second : std::string{};
337 auto get_int64 = [&row](
const std::string& key) -> int64_t {
338 auto it = row.find(key);
339 if (it != row.end() && !it->second.empty()) {
341 return std::stoll(it->second);
349 record.pk = get_int64(
"patient_pk");
350 record.patient_id = get_str(
"patient_id");
351 record.patient_name = get_str(
"patient_name");
352 record.birth_date = get_str(
"birth_date");
353 record.sex = get_str(
"sex");
354 record.other_ids = get_str(
"other_ids");
355 record.ethnic_group = get_str(
"ethnic_group");
356 record.comments = get_str(
"comments");
358 auto created_at_str = get_str(
"created_at");
359 if (!created_at_str.empty()) {
360 record.created_at = parse_datetime(created_at_str.c_str());
363 auto updated_at_str = get_str(
"updated_at");
364 if (!updated_at_str.empty()) {
365 record.updated_at = parse_datetime(updated_at_str.c_str());
371auto index_database::parse_study_from_adapter_row(
const database_row& row)
const
375 auto get_str = [&row](
const std::string& key) -> std::string {
376 auto it = row.find(key);
377 return (it != row.end()) ? it->second : std::string{};
380 auto get_int64 = [&row](
const std::string& key) -> int64_t {
381 auto it = row.find(key);
382 if (it != row.end() && !it->second.empty()) {
384 return std::stoll(it->second);
392 auto get_optional_int = [&row](
const std::string& key) -> std::optional<int> {
393 auto it = row.find(key);
394 if (it != row.end() && !it->second.empty()) {
396 return std::stoi(it->second);
404 record.pk = get_int64(
"study_pk");
405 record.patient_pk = get_int64(
"patient_pk");
406 record.study_uid = get_str(
"study_uid");
407 record.study_id = get_str(
"study_id");
408 record.study_date = get_str(
"study_date");
409 record.study_time = get_str(
"study_time");
410 record.accession_number = get_str(
"accession_number");
411 record.referring_physician = get_str(
"referring_physician");
412 record.study_description = get_str(
"study_description");
413 record.modalities_in_study = get_str(
"modalities_in_study");
414 record.num_instances = get_optional_int(
"num_instances").value_or(0);
415 record.num_series = get_optional_int(
"num_series").value_or(0);
417 auto created_at_str = get_str(
"created_at");
418 if (!created_at_str.empty()) {
419 record.created_at = parse_datetime(created_at_str.c_str());
422 auto updated_at_str = get_str(
"updated_at");
423 if (!updated_at_str.empty()) {
424 record.updated_at = parse_datetime(updated_at_str.c_str());
430auto index_database::parse_series_from_adapter_row(
const database_row& row)
const
434 auto get_str = [&row](
const std::string& key) -> std::string {
435 auto it = row.find(key);
436 return (it != row.end()) ? it->second : std::string{};
439 auto get_int64 = [&row](
const std::string& key) -> int64_t {
440 auto it = row.find(key);
441 if (it != row.end() && !it->second.empty()) {
443 return std::stoll(it->second);
451 auto get_optional_int = [&row](
const std::string& key) -> std::optional<int> {
452 auto it = row.find(key);
453 if (it != row.end() && !it->second.empty()) {
455 return std::stoi(it->second);
463 record.pk = get_int64(
"series_pk");
464 record.study_pk = get_int64(
"study_pk");
465 record.series_uid = get_str(
"series_uid");
466 record.modality = get_str(
"modality");
467 record.series_number = get_optional_int(
"series_number");
468 record.series_description = get_str(
"series_description");
469 record.body_part_examined = get_str(
"body_part_examined");
470 record.station_name = get_str(
"station_name");
471 record.num_instances = get_optional_int(
"num_instances").value_or(0);
473 auto created_at_str = get_str(
"created_at");
474 if (!created_at_str.empty()) {
475 record.created_at = parse_datetime(created_at_str.c_str());
478 auto updated_at_str = get_str(
"updated_at");
479 if (!updated_at_str.empty()) {
480 record.updated_at = parse_datetime(updated_at_str.c_str());
486auto index_database::parse_instance_from_adapter_row(
const database_row& row)
const
490 auto get_str = [&row](
const std::string& key) -> std::string {
491 auto it = row.find(key);
492 return (it != row.end()) ? it->second : std::string{};
495 auto get_int64 = [&row](
const std::string& key) -> int64_t {
496 auto it = row.find(key);
497 if (it != row.end() && !it->second.empty()) {
499 return std::stoll(it->second);
507 auto get_optional_int = [&row](
const std::string& key) -> std::optional<int> {
508 auto it = row.find(key);
509 if (it != row.end() && !it->second.empty()) {
511 return std::stoi(it->second);
519 record.pk = get_int64(
"instance_pk");
520 record.series_pk = get_int64(
"series_pk");
521 record.sop_uid = get_str(
"sop_uid");
522 record.sop_class_uid = get_str(
"sop_class_uid");
523 record.instance_number = get_optional_int(
"instance_number");
524 record.transfer_syntax = get_str(
"transfer_syntax");
525 record.content_date = get_str(
"content_date");
526 record.content_time = get_str(
"content_time");
527 record.rows = get_optional_int(
"rows");
528 record.columns = get_optional_int(
"columns");
529 record.bits_allocated = get_optional_int(
"bits_allocated");
530 record.number_of_frames = get_optional_int(
"number_of_frames");
531 record.file_path = get_str(
"file_path");
532 record.file_size = get_int64(
"file_size");
533 record.file_hash = get_str(
"file_hash");
535 auto created_at_str = get_str(
"created_at");
536 if (!created_at_str.empty()) {
537 record.created_at = parse_datetime(created_at_str.c_str());
543auto index_database::parse_mpps_from_adapter_row(
const database_row& row)
const
547 auto get_str = [&row](
const std::string& key) -> std::string {
548 auto it = row.find(key);
549 return (it != row.end()) ? it->second : std::string{};
552 auto get_int64 = [&row](
const std::string& key) -> int64_t {
553 auto it = row.find(key);
554 if (it != row.end() && !it->second.empty()) {
556 return std::stoll(it->second);
564 record.pk = get_int64(
"mpps_pk");
565 record.mpps_uid = get_str(
"mpps_uid");
566 record.status = get_str(
"status");
567 record.start_datetime = get_str(
"start_datetime");
568 record.end_datetime = get_str(
"end_datetime");
569 record.station_ae = get_str(
"station_ae");
570 record.station_name = get_str(
"station_name");
571 record.modality = get_str(
"modality");
572 record.study_uid = get_str(
"study_uid");
573 record.accession_no = get_str(
"accession_no");
574 record.scheduled_step_id = get_str(
"scheduled_step_id");
575 record.requested_proc_id = get_str(
"requested_proc_id");
576 record.performed_series = get_str(
"performed_series");
578 auto created_at_str = get_str(
"created_at");
579 if (!created_at_str.empty()) {
580 record.created_at = parse_datetime(created_at_str.c_str());
583 auto updated_at_str = get_str(
"updated_at");
584 if (!updated_at_str.empty()) {
585 record.updated_at = parse_datetime(updated_at_str.c_str());
591auto index_database::parse_worklist_from_adapter_row(
const database_row& row)
const
595 auto get_str = [&row](
const std::string& key) -> std::string {
596 auto it = row.find(key);
597 return (it != row.end()) ? it->second : std::string{};
600 auto get_int64 = [&row](
const std::string& key) -> int64_t {
601 auto it = row.find(key);
602 if (it != row.end() && !it->second.empty()) {
604 return std::stoll(it->second);
612 item.pk = get_int64(
"worklist_pk");
613 item.step_id = get_str(
"step_id");
614 item.step_status = get_str(
"step_status");
615 item.patient_id = get_str(
"patient_id");
616 item.patient_name = get_str(
"patient_name");
617 item.birth_date = get_str(
"birth_date");
618 item.sex = get_str(
"sex");
619 item.accession_no = get_str(
"accession_no");
620 item.requested_proc_id = get_str(
"requested_proc_id");
621 item.study_uid = get_str(
"study_uid");
622 item.scheduled_datetime = get_str(
"scheduled_datetime");
623 item.station_ae = get_str(
"station_ae");
624 item.station_name = get_str(
"station_name");
625 item.modality = get_str(
"modality");
626 item.procedure_desc = get_str(
"procedure_desc");
627 item.protocol_code = get_str(
"protocol_code");
628 item.referring_phys = get_str(
"referring_phys");
629 item.referring_phys_id = get_str(
"referring_phys_id");
631 auto created_at_str = get_str(
"created_at");
632 if (!created_at_str.empty()) {
633 item.created_at = parse_datetime(created_at_str.c_str());
636 auto updated_at_str = get_str(
"updated_at");
637 if (!updated_at_str.empty()) {
638 item.updated_at = parse_datetime(updated_at_str.c_str());
646#ifdef PACS_WITH_DATABASE_SYSTEM
647 if (!db_adapter_ || !db_adapter_->is_connected()) {
648 return make_error<std::monostate>(
650 "PACS database adapter is not connected",
654 patient_repository_ = std::make_shared<patient_repository>(db_adapter_);
655 study_repository_ = std::make_shared<study_repository>(db_adapter_);
656 series_repository_ = std::make_shared<series_repository>(db_adapter_);
657 instance_repository_ = std::make_shared<instance_repository>(db_adapter_);
658 mpps_repository_ = std::make_shared<mpps_repository>(db_adapter_);
659 worklist_repository_ = std::make_shared<worklist_repository>(db_adapter_);
660 ups_repository_ = std::make_shared<ups_repository>(db_adapter_);
661 audit_repository_ = std::make_shared<audit_repository>(db_adapter_);
663 if (db_ ==
nullptr) {
664 return make_error<std::monostate>(
666 "SQLite database handle is not available",
670 patient_repository_ = std::make_shared<patient_repository>(db_);
671 study_repository_ = std::make_shared<study_repository>(db_);
672 series_repository_ = std::make_shared<series_repository>(db_);
673 instance_repository_ = std::make_shared<instance_repository>(db_);
674 mpps_repository_ = std::make_shared<mpps_repository>(db_);
675 worklist_repository_ = std::make_shared<worklist_repository>(db_);
676 ups_repository_ = std::make_shared<ups_repository>(db_);
677 audit_repository_ = std::make_shared<audit_repository>(db_);
685 path_(std::move(other.path_)),
686 remove_on_close_(other.remove_on_close_),
687 patient_repository_(std::move(other.patient_repository_)),
688 study_repository_(std::move(other.study_repository_)),
689 series_repository_(std::move(other.series_repository_)),
690 instance_repository_(std::move(other.instance_repository_)),
691 mpps_repository_(std::move(other.mpps_repository_)),
692 worklist_repository_(std::move(other.worklist_repository_)),
693 ups_repository_(std::move(other.ups_repository_)),
694 audit_repository_(std::move(other.audit_repository_)) {
695#ifdef PACS_WITH_DATABASE_SYSTEM
696 db_adapter_ = std::move(other.db_adapter_);
699 other.remove_on_close_ =
false;
704 if (
this != &other) {
705 patient_repository_.reset();
706 study_repository_.reset();
707 series_repository_.reset();
708 instance_repository_.reset();
709 mpps_repository_.reset();
710 worklist_repository_.reset();
711 ups_repository_.reset();
712 audit_repository_.reset();
713#ifdef PACS_WITH_DATABASE_SYSTEM
715 (void)db_adapter_->disconnect();
717 db_adapter_ = std::move(other.db_adapter_);
722 if (remove_on_close_) {
723 remove_database_sidecars(path_);
726 path_ = std::move(other.path_);
727 remove_on_close_ = other.remove_on_close_;
728 patient_repository_ = std::move(other.patient_repository_);
729 study_repository_ = std::move(other.study_repository_);
730 series_repository_ = std::move(other.series_repository_);
731 instance_repository_ = std::move(other.instance_repository_);
732 mpps_repository_ = std::move(other.mpps_repository_);
733 worklist_repository_ = std::move(other.worklist_repository_);
734 ups_repository_ = std::move(other.ups_repository_);
735 audit_repository_ = std::move(other.audit_repository_);
737 other.remove_on_close_ =
false;
747 std::string_view patient_name,
748 std::string_view birth_date,
750 return patient_repository_->upsert_patient(patient_id, patient_name,
756 return patient_repository_->upsert_patient(record);
760 -> std::optional<patient_record> {
761 return patient_repository_->find_patient(patient_id);
765 -> std::optional<patient_record> {
766 return patient_repository_->find_patient_by_pk(pk);
771 return patient_repository_->search_patients(query);
775 return patient_repository_->delete_patient(patient_id);
799 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
802 record.pk = sqlite3_column_int64(stmt, 0);
803 record.patient_id = get_text(stmt, 1);
804 record.patient_name = get_text(stmt, 2);
805 record.birth_date = get_text(stmt, 3);
806 record.sex = get_text(stmt, 4);
807 record.other_ids = get_text(stmt, 5);
808 record.ethnic_group = get_text(stmt, 6);
809 record.comments = get_text(stmt, 7);
811 auto created_str = get_text(stmt, 8);
812 record.created_at = parse_datetime(created_str.c_str());
814 auto updated_str = get_text(stmt, 9);
815 record.updated_at = parse_datetime(updated_str.c_str());
822 result.reserve(pattern.size());
824 for (
char c : pattern) {
827 }
else if (c ==
'?') {
829 }
else if (c ==
'%' || c ==
'_') {
846 std::string_view study_uid,
847 std::string_view study_id,
848 std::string_view study_date,
849 std::string_view study_time,
850 std::string_view accession_number,
851 std::string_view referring_physician,
852 std::string_view study_description)
854 return study_repository_->upsert_study(
855 patient_pk, study_uid, study_id, study_date, study_time,
856 accession_number, referring_physician, study_description);
861 return study_repository_->upsert_study(record);
865 -> std::optional<study_record> {
866 return study_repository_->find_study(study_uid);
870 -> std::optional<study_record> {
871 return study_repository_->find_study_by_pk(pk);
877 query.patient_id = std::string(patient_id);
878 return study_repository_->search_studies(query);
883 return study_repository_->search_studies(query);
887 return study_repository_->delete_study(study_uid);
895 auto patient = patient_repository_->find_patient(patient_id);
896 if (!patient.has_value()) {
897 return ok(
static_cast<size_t>(0));
899 return study_repository_->study_count_for_patient(patient->pk);
904 return study_repository_->update_modalities_in_study(study_pk);
908 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
911 record.pk = sqlite3_column_int64(stmt, 0);
912 record.patient_pk = sqlite3_column_int64(stmt, 1);
913 record.study_uid = get_text(stmt, 2);
914 record.study_id = get_text(stmt, 3);
915 record.study_date = get_text(stmt, 4);
916 record.study_time = get_text(stmt, 5);
917 record.accession_number = get_text(stmt, 6);
918 record.referring_physician = get_text(stmt, 7);
919 record.study_description = get_text(stmt, 8);
920 record.modalities_in_study = get_text(stmt, 9);
921 record.num_series = sqlite3_column_int(stmt, 10);
922 record.num_instances = sqlite3_column_int(stmt, 11);
924 auto created_str = get_text(stmt, 12);
925 record.created_at = parse_datetime(created_str.c_str());
927 auto updated_str = get_text(stmt, 13);
928 record.updated_at = parse_datetime(updated_str.c_str());
938 std::string_view series_uid,
939 std::string_view modality,
940 std::optional<int> series_number,
941 std::string_view series_description,
942 std::string_view body_part_examined,
943 std::string_view station_name)
946 record.study_pk = study_pk;
947 record.series_uid = std::string(series_uid);
948 record.modality = std::string(modality);
949 record.series_number = series_number;
950 record.series_description = std::string(series_description);
951 record.body_part_examined = std::string(body_part_examined);
952 record.station_name = std::string(station_name);
953 return upsert_series(record);
958 auto existing = find_series(record.series_uid);
959 auto result = series_repository_->upsert_series(record);
960 if (result.is_err()) {
964 if (existing.has_value() && existing->study_pk > 0 &&
965 existing->study_pk != record.study_pk) {
966 (void)update_modalities_in_study(existing->study_pk);
968 if (record.study_pk > 0) {
969 (void)update_modalities_in_study(record.study_pk);
976 -> std::optional<series_record> {
977 return series_repository_->find_series(series_uid);
981 -> std::optional<series_record> {
982 return series_repository_->find_series_by_pk(pk);
987 return series_repository_->list_series(study_uid);
992 return series_repository_->search_series(query);
996 auto existing = find_series(series_uid);
997 auto result = series_repository_->delete_series(series_uid);
998 if (result.is_ok() && existing.has_value() && existing->study_pk > 0) {
999 (void)update_modalities_in_study(existing->study_pk);
1009 return series_repository_->series_count(study_uid);
1013 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
1016 record.pk = sqlite3_column_int64(stmt, 0);
1017 record.study_pk = sqlite3_column_int64(stmt, 1);
1018 record.series_uid = get_text(stmt, 2);
1019 record.modality = get_text(stmt, 3);
1022 if (sqlite3_column_type(stmt, 4) != SQLITE_NULL) {
1023 record.series_number = sqlite3_column_int(stmt, 4);
1026 record.series_description = get_text(stmt, 5);
1027 record.body_part_examined = get_text(stmt, 6);
1028 record.station_name = get_text(stmt, 7);
1029 record.num_instances = sqlite3_column_int(stmt, 8);
1031 auto created_str = get_text(stmt, 9);
1032 record.created_at = parse_datetime(created_str.c_str());
1034 auto updated_str = get_text(stmt, 10);
1035 record.updated_at = parse_datetime(updated_str.c_str());
1045 std::string_view sop_uid,
1046 std::string_view sop_class_uid,
1047 std::string_view file_path,
1049 std::string_view transfer_syntax,
1050 std::optional<int> instance_number)
1052 return instance_repository_->upsert_instance(
1053 series_pk, sop_uid, sop_class_uid, file_path, file_size,
1054 transfer_syntax, instance_number);
1059 return instance_repository_->upsert_instance(record);
1063 -> std::optional<instance_record> {
1064 return instance_repository_->find_instance(sop_uid);
1068 -> std::optional<instance_record> {
1069 return instance_repository_->find_instance_by_pk(pk);
1074 return instance_repository_->list_instances(series_uid);
1079 return instance_repository_->search_instances(query);
1083 return instance_repository_->delete_instance(sop_uid);
1092 return instance_repository_->instance_count(series_uid);
1097 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
1100 record.pk = sqlite3_column_int64(stmt, 0);
1101 record.series_pk = sqlite3_column_int64(stmt, 1);
1102 record.sop_uid = get_text(stmt, 2);
1103 record.sop_class_uid = get_text(stmt, 3);
1106 if (sqlite3_column_type(stmt, 4) != SQLITE_NULL) {
1107 record.instance_number = sqlite3_column_int(stmt, 4);
1110 record.transfer_syntax = get_text(stmt, 5);
1111 record.content_date = get_text(stmt, 6);
1112 record.content_time = get_text(stmt, 7);
1115 if (sqlite3_column_type(stmt, 8) != SQLITE_NULL) {
1116 record.rows = sqlite3_column_int(stmt, 8);
1119 if (sqlite3_column_type(stmt, 9) != SQLITE_NULL) {
1120 record.columns = sqlite3_column_int(stmt, 9);
1123 if (sqlite3_column_type(stmt, 10) != SQLITE_NULL) {
1124 record.bits_allocated = sqlite3_column_int(stmt, 10);
1127 if (sqlite3_column_type(stmt, 11) != SQLITE_NULL) {
1128 record.number_of_frames = sqlite3_column_int(stmt, 11);
1131 record.file_path = get_text(stmt, 12);
1132 record.file_size = sqlite3_column_int64(stmt, 13);
1133 record.file_hash = get_text(stmt, 14);
1135 auto created_str = get_text(stmt, 15);
1136 record.created_at = parse_datetime(created_str.c_str());
1147 return instance_repository_->get_file_path(sop_instance_uid);
1152 return instance_repository_->get_study_files(study_instance_uid);
1157 return instance_repository_->get_series_files(series_instance_uid);
1165#ifdef PACS_WITH_DATABASE_SYSTEM
1167 if (db_adapter_ && db_adapter_->is_connected()) {
1168 return db_adapter_->execute(
"VACUUM;");
1172 auto rc = sqlite3_exec(db_,
"VACUUM;",
nullptr,
nullptr,
nullptr);
1173 if (rc != SQLITE_OK) {
1174 return make_error<std::monostate>(
1175 rc, kcenon::pacs::compat::format(
"VACUUM failed: {}", sqlite3_errmsg(db_)),
1182#ifdef PACS_WITH_DATABASE_SYSTEM
1184 if (db_adapter_ && db_adapter_->is_connected()) {
1185 return db_adapter_->execute(
"ANALYZE;");
1189 auto rc = sqlite3_exec(db_,
"ANALYZE;",
nullptr,
nullptr,
nullptr);
1190 if (rc != SQLITE_OK) {
1191 return make_error<std::monostate>(
1192 rc, kcenon::pacs::compat::format(
"ANALYZE failed: {}", sqlite3_errmsg(db_)),
1199#ifdef PACS_WITH_DATABASE_SYSTEM
1201 if (db_adapter_ && db_adapter_->is_connected()) {
1202 auto result = db_adapter_->select(
"PRAGMA integrity_check;");
1203 if (result.is_err()) {
1204 return make_error<std::monostate>(
1205 result.error().code, result.error().message,
"storage");
1208 for (
const auto& row : result.value()) {
1209 auto it = row.find(
"integrity_check");
1210 if (it != row.end() && it->second !=
"ok") {
1211 return make_error<std::monostate>(
1212 -1, kcenon::pacs::compat::format(
"Integrity check failed: {}", it->second),
1220 const char* sql =
"PRAGMA integrity_check;";
1222 sqlite3_stmt* stmt =
nullptr;
1223 auto rc = sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr);
1224 if (rc != SQLITE_OK) {
1225 return make_error<std::monostate>(
1227 kcenon::pacs::compat::format(
"Failed to prepare integrity check: {}",
1228 sqlite3_errmsg(
db_)),
1233 while (sqlite3_step(stmt) == SQLITE_ROW) {
1234 result = get_text(stmt, 0);
1235 if (result !=
"ok") {
1236 sqlite3_finalize(stmt);
1237 return make_error<std::monostate>(
1238 -1, kcenon::pacs::compat::format(
"Integrity check failed: {}", result),
1243 sqlite3_finalize(stmt);
1249 truncate ?
"PRAGMA wal_checkpoint(TRUNCATE);"
1250 :
"PRAGMA wal_checkpoint(PASSIVE);";
1252#ifdef PACS_WITH_DATABASE_SYSTEM
1254 if (db_adapter_ && db_adapter_->is_connected()) {
1255 return db_adapter_->execute(sql);
1259 auto rc = sqlite3_exec(db_, sql,
nullptr,
nullptr,
nullptr);
1260 if (rc != SQLITE_OK) {
1261 return make_error<std::monostate>(
1262 rc, kcenon::pacs::compat::format(
"Checkpoint failed: {}", sqlite3_errmsg(db_)),
1272#ifdef PACS_WITH_DATABASE_SYSTEM
1273auto index_database::db_adapter() const noexcept
1274 -> std::shared_ptr<pacs_database_adapter> {
1287 if (patient_count_result.is_err()) {
1290 stats.total_patients = patient_count_result.value();
1293 if (study_count_result.is_err()) {
1296 stats.total_studies = study_count_result.value();
1299 if (series_count_result.is_err()) {
1302 stats.total_series = series_count_result.value();
1305 if (instance_count_result.is_err()) {
1308 stats.total_instances = instance_count_result.value();
1311 const char* file_size_sql =
1312 "SELECT COALESCE(SUM(file_size), 0) AS total_size FROM instances;";
1314 sqlite3_stmt* stmt =
nullptr;
1315 auto rc = sqlite3_prepare_v2(
db_, file_size_sql, -1, &stmt,
nullptr);
1316 if (rc != SQLITE_OK) {
1319 kcenon::pacs::compat::format(
"Failed to prepare query: {}",
1320 sqlite3_errmsg(
db_)));
1323 if (sqlite3_step(stmt) == SQLITE_ROW) {
1324 stats.total_file_size = sqlite3_column_int64(stmt, 0);
1326 sqlite3_finalize(stmt);
1329 if (
path_ !=
":memory:") {
1331 auto size = std::filesystem::file_size(
path_, ec);
1333 stats.database_size =
static_cast<int64_t
>(size);
1337 return ok(std::move(stats));
1345 std::string_view station_ae,
1346 std::string_view modality,
1347 std::string_view study_uid,
1348 std::string_view accession_no,
1349 std::string_view start_datetime)
1351 return mpps_repository_->create_mpps(mpps_uid, station_ae, modality,
1352 study_uid, accession_no,
1357 return mpps_repository_->create_mpps(record);
1361 std::string_view new_status,
1362 std::string_view end_datetime,
1363 std::string_view performed_series)
1365 return mpps_repository_->update_mpps(mpps_uid, new_status, end_datetime,
1370 return mpps_repository_->update_mpps(record);
1374 -> std::optional<mpps_record> {
1375 return mpps_repository_->find_mpps(mpps_uid);
1379 -> std::optional<mpps_record> {
1380 return mpps_repository_->find_mpps_by_pk(pk);
1385 return mpps_repository_->list_active_mpps(station_ae);
1390 return mpps_repository_->find_mpps_by_study(study_uid);
1395 return mpps_repository_->search_mpps(query);
1399 return mpps_repository_->delete_mpps(mpps_uid);
1407 return mpps_repository_->mpps_count(status);
1411 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
1414 record.pk = sqlite3_column_int64(stmt, 0);
1415 record.mpps_uid = get_text(stmt, 1);
1416 record.status = get_text(stmt, 2);
1417 record.start_datetime = get_text(stmt, 3);
1418 record.end_datetime = get_text(stmt, 4);
1419 record.station_ae = get_text(stmt, 5);
1420 record.station_name = get_text(stmt, 6);
1421 record.modality = get_text(stmt, 7);
1422 record.study_uid = get_text(stmt, 8);
1423 record.accession_no = get_text(stmt, 9);
1424 record.scheduled_step_id = get_text(stmt, 10);
1425 record.requested_proc_id = get_text(stmt, 11);
1426 record.performed_series = get_text(stmt, 12);
1428 auto created_str = get_text(stmt, 13);
1429 record.created_at = parse_datetime(created_str.c_str());
1431 auto updated_str = get_text(stmt, 14);
1432 record.updated_at = parse_datetime(updated_str.c_str());
1437#ifdef PACS_WITH_DATABASE_SYSTEM
1438auto index_database::parse_mpps_from_row(
1439 const std::map<std::string, database::core::database_value>& row)
const
1443 record.pk = get_int64_value(row,
"mpps_pk");
1444 record.mpps_uid = get_string_value(row,
"mpps_uid");
1445 record.status = get_string_value(row,
"status");
1446 record.start_datetime = get_string_value(row,
"start_datetime");
1447 record.end_datetime = get_string_value(row,
"end_datetime");
1448 record.station_ae = get_string_value(row,
"station_ae");
1449 record.station_name = get_string_value(row,
"station_name");
1450 record.modality = get_string_value(row,
"modality");
1451 record.study_uid = get_string_value(row,
"study_uid");
1452 record.accession_no = get_string_value(row,
"accession_no");
1453 record.scheduled_step_id = get_string_value(row,
"scheduled_step_id");
1454 record.requested_proc_id = get_string_value(row,
"requested_proc_id");
1455 record.performed_series = get_string_value(row,
"performed_series");
1457 auto created_str = get_string_value(row,
"created_at");
1458 record.created_at = parse_datetime(created_str.c_str());
1460 auto updated_str = get_string_value(row,
"updated_at");
1461 record.updated_at = parse_datetime(updated_str.c_str());
1466auto index_database::parse_worklist_from_row(
1467 const std::map<std::string, database::core::database_value>& row)
const
1471 item.pk = get_int64_value(row,
"worklist_pk");
1472 item.step_id = get_string_value(row,
"step_id");
1473 item.step_status = get_string_value(row,
"step_status");
1474 item.patient_id = get_string_value(row,
"patient_id");
1475 item.patient_name = get_string_value(row,
"patient_name");
1476 item.birth_date = get_string_value(row,
"birth_date");
1477 item.sex = get_string_value(row,
"sex");
1478 item.accession_no = get_string_value(row,
"accession_no");
1479 item.requested_proc_id = get_string_value(row,
"requested_proc_id");
1480 item.study_uid = get_string_value(row,
"study_uid");
1481 item.scheduled_datetime = get_string_value(row,
"scheduled_datetime");
1482 item.station_ae = get_string_value(row,
"station_ae");
1483 item.station_name = get_string_value(row,
"station_name");
1484 item.modality = get_string_value(row,
"modality");
1485 item.procedure_desc = get_string_value(row,
"procedure_desc");
1486 item.protocol_code = get_string_value(row,
"protocol_code");
1487 item.referring_phys = get_string_value(row,
"referring_phys");
1488 item.referring_phys_id = get_string_value(row,
"referring_phys_id");
1490 auto created_str = get_string_value(row,
"created_at");
1491 item.created_at = parse_datetime(created_str.c_str());
1493 auto updated_str = get_string_value(row,
"updated_at");
1494 item.updated_at = parse_datetime(updated_str.c_str());
1499auto index_database::parse_audit_from_row(
1500 const std::map<std::string, database::core::database_value>& row)
const
1502 audit_record record;
1504 record.pk = get_int64_value(row,
"audit_pk");
1505 record.event_type = get_string_value(row,
"event_type");
1506 record.outcome = get_string_value(row,
"outcome");
1508 auto timestamp_str = get_string_value(row,
"timestamp");
1509 record.timestamp = parse_datetime(timestamp_str.c_str());
1511 record.user_id = get_string_value(row,
"user_id");
1512 record.source_ae = get_string_value(row,
"source_ae");
1513 record.target_ae = get_string_value(row,
"target_ae");
1514 record.source_ip = get_string_value(row,
"source_ip");
1515 record.patient_id = get_string_value(row,
"patient_id");
1516 record.study_uid = get_string_value(row,
"study_uid");
1517 record.message = get_string_value(row,
"message");
1518 record.details = get_string_value(row,
"details");
1530 return worklist_repository_->add_worklist_item(item);
1534 std::string_view accession_no,
1535 std::string_view new_status)
1537 return worklist_repository_->update_worklist_status(step_id, accession_no,
1543 return worklist_repository_->query_worklist(query);
1547 std::string_view accession_no)
const
1548 -> std::optional<worklist_item> {
1549 return worklist_repository_->find_worklist_item(step_id, accession_no);
1553 -> std::optional<worklist_item> {
1554 return worklist_repository_->find_worklist_by_pk(pk);
1558 std::string_view accession_no)
1560 return worklist_repository_->delete_worklist_item(step_id, accession_no);
1565 return worklist_repository_->cleanup_old_worklist_items(age);
1569 std::chrono::system_clock::time_point before) ->
Result<size_t> {
1570 return worklist_repository_->cleanup_worklist_items_before(before);
1578 return worklist_repository_->worklist_count(status);
1582 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
1585 item.pk = sqlite3_column_int64(stmt, 0);
1586 item.step_id = get_text(stmt, 1);
1587 item.step_status = get_text(stmt, 2);
1588 item.patient_id = get_text(stmt, 3);
1589 item.patient_name = get_text(stmt, 4);
1590 item.birth_date = get_text(stmt, 5);
1591 item.sex = get_text(stmt, 6);
1592 item.accession_no = get_text(stmt, 7);
1593 item.requested_proc_id = get_text(stmt, 8);
1594 item.study_uid = get_text(stmt, 9);
1595 item.scheduled_datetime = get_text(stmt, 10);
1596 item.station_ae = get_text(stmt, 11);
1597 item.station_name = get_text(stmt, 12);
1598 item.modality = get_text(stmt, 13);
1599 item.procedure_desc = get_text(stmt, 14);
1600 item.protocol_code = get_text(stmt, 15);
1601 item.referring_phys = get_text(stmt, 16);
1602 item.referring_phys_id = get_text(stmt, 17);
1604 auto created_str = get_text(stmt, 18);
1605 item.created_at = parse_datetime(created_str.c_str());
1607 auto updated_str = get_text(stmt, 19);
1608 item.updated_at = parse_datetime(updated_str.c_str());
1619 return ups_repository_->create_ups_workitem(workitem);
1624 return ups_repository_->update_ups_workitem(workitem);
1628 std::string_view new_state,
1629 std::string_view transaction_uid)
1631 return ups_repository_->change_ups_state(workitem_uid, new_state,
1636 -> std::optional<ups_workitem> {
1637 return ups_repository_->find_ups_workitem(workitem_uid);
1642 return ups_repository_->search_ups_workitems(query);
1647 return ups_repository_->delete_ups_workitem(workitem_uid);
1656 return ups_repository_->ups_workitem_count(state);
1665 return ups_repository_->subscribe_ups(subscription);
1669 std::string_view workitem_uid)
1671 return ups_repository_->unsubscribe_ups(subscriber_ae, workitem_uid);
1676 return ups_repository_->get_ups_subscriptions(subscriber_ae);
1681 return ups_repository_->get_ups_subscribers(workitem_uid);
1685 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
1688 item.pk = sqlite3_column_int64(stmt, 0);
1689 item.workitem_uid = get_text(stmt, 1);
1690 item.state = get_text(stmt, 2);
1691 item.procedure_step_label = get_text(stmt, 3);
1692 item.worklist_label = get_text(stmt, 4);
1693 item.priority = get_text(stmt, 5);
1694 item.scheduled_start_datetime = get_text(stmt, 6);
1695 item.expected_completion_datetime = get_text(stmt, 7);
1696 item.scheduled_station_name = get_text(stmt, 8);
1697 item.scheduled_station_class = get_text(stmt, 9);
1698 item.scheduled_station_geographic = get_text(stmt, 10);
1699 item.scheduled_human_performers = get_text(stmt, 11);
1700 item.input_information = get_text(stmt, 12);
1701 item.performing_ae = get_text(stmt, 13);
1702 item.progress_description = get_text(stmt, 14);
1703 item.progress_percent = sqlite3_column_int(stmt, 15);
1704 item.output_information = get_text(stmt, 16);
1705 item.transaction_uid = get_text(stmt, 17);
1707 auto created_str = get_text(stmt, 18);
1708 item.created_at = parse_datetime(created_str.c_str());
1710 auto updated_str = get_text(stmt, 19);
1711 item.updated_at = parse_datetime(updated_str.c_str());
1722 return audit_repository_->add_audit_log(record);
1727 return audit_repository_->query_audit_log(query);
1731 -> std::optional<audit_record> {
1732 return audit_repository_->find_audit_by_pk(pk);
1741 return audit_repository_->cleanup_old_audit_logs(age);
1745 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
1748 record.pk = sqlite3_column_int64(stmt, 0);
1749 record.event_type = get_text(stmt, 1);
1750 record.outcome = get_text(stmt, 2);
1752 auto timestamp_str = get_text(stmt, 3);
1753 record.timestamp = parse_datetime(timestamp_str.c_str());
1755 record.user_id = get_text(stmt, 4);
1756 record.source_ae = get_text(stmt, 5);
1757 record.target_ae = get_text(stmt, 6);
1758 record.source_ip = get_text(stmt, 7);
1759 record.patient_id = get_text(stmt, 8);
1760 record.study_uid = get_text(stmt, 9);
1761 record.message = get_text(stmt, 10);
1762 record.details = get_text(stmt, 11);
1767#ifdef PACS_WITH_DATABASE_SYSTEM
1768auto index_database::parse_patient_from_row(
1769 const std::map<std::string, database::core::database_value>& row)
const
1773 record.pk = get_int64_value(row,
"patient_pk");
1774 record.patient_id = get_string_value(row,
"patient_id");
1775 record.patient_name = get_string_value(row,
"patient_name");
1776 record.birth_date = get_string_value(row,
"birth_date");
1777 record.sex = get_string_value(row,
"sex");
1778 record.other_ids = get_string_value(row,
"other_ids");
1779 record.ethnic_group = get_string_value(row,
"ethnic_group");
1780 record.comments = get_string_value(row,
"comments");
1782 auto created_str = get_string_value(row,
"created_at");
1783 record.created_at = parse_datetime(created_str.c_str());
1785 auto updated_str = get_string_value(row,
"updated_at");
1786 record.updated_at = parse_datetime(updated_str.c_str());
1791auto index_database::parse_study_from_row(
1792 const std::map<std::string, database::core::database_value>& row)
const
1794 study_record record;
1796 record.pk = get_int64_value(row,
"study_pk");
1797 record.patient_pk = get_int64_value(row,
"patient_pk");
1798 record.study_uid = get_string_value(row,
"study_uid");
1799 record.study_id = get_string_value(row,
"study_id");
1800 record.study_date = get_string_value(row,
"study_date");
1801 record.study_time = get_string_value(row,
"study_time");
1802 record.accession_number = get_string_value(row,
"accession_number");
1803 record.referring_physician = get_string_value(row,
"referring_physician");
1804 record.study_description = get_string_value(row,
"study_description");
1805 record.modalities_in_study = get_string_value(row,
"modalities_in_study");
1806 record.num_series =
static_cast<int>(get_int64_value(row,
"num_series"));
1807 record.num_instances =
1808 static_cast<int>(get_int64_value(row,
"num_instances"));
1810 auto created_str = get_string_value(row,
"created_at");
1811 record.created_at = parse_datetime(created_str.c_str());
1813 auto updated_str = get_string_value(row,
"updated_at");
1814 record.updated_at = parse_datetime(updated_str.c_str());
1819auto index_database::parse_series_from_row(
1820 const std::map<std::string, database::core::database_value>& row)
const
1822 series_record record;
1824 record.pk = get_int64_value(row,
"series_pk");
1825 record.study_pk = get_int64_value(row,
"study_pk");
1826 record.series_uid = get_string_value(row,
"series_uid");
1827 record.modality = get_string_value(row,
"modality");
1830 auto it = row.find(
"series_number");
1831 if (it != row.end()) {
1832 if (std::holds_alternative<int64_t>(it->second)) {
1833 record.series_number =
static_cast<int>(std::get<int64_t>(it->second));
1834 }
else if (std::holds_alternative<std::string>(it->second)) {
1835 const auto& str = std::get<std::string>(it->second);
1837 record.series_number = std::stoi(str);
1842 record.series_description = get_string_value(row,
"series_description");
1843 record.body_part_examined = get_string_value(row,
"body_part_examined");
1844 record.station_name = get_string_value(row,
"station_name");
1846 static_cast<int>(get_int64_value(row,
"num_instances"));
1848 auto created_str = get_string_value(row,
"created_at");
1849 record.created_at = parse_datetime(created_str.c_str());
1851 auto updated_str2 = get_string_value(row,
"updated_at");
1852 record.updated_at = parse_datetime(updated_str2.c_str());
1857auto index_database::parse_instance_from_row(
1858 const std::map<std::string, database::core::database_value>& row)
const
1859 -> instance_record {
1862 record.pk = get_int64_value(row,
"instance_pk");
1863 record.series_pk = get_int64_value(row,
"series_pk");
1864 record.sop_uid = get_string_value(row,
"sop_uid");
1865 record.sop_class_uid = get_string_value(row,
"sop_class_uid");
1868 auto it = row.find(
"instance_number");
1869 if (it != row.end()) {
1870 if (std::holds_alternative<int64_t>(it->second)) {
1871 record.instance_number =
static_cast<int>(std::get<int64_t>(it->second));
1872 }
else if (std::holds_alternative<std::string>(it->second)) {
1873 const auto& str = std::get<std::string>(it->second);
1875 record.instance_number = std::stoi(str);
1880 record.transfer_syntax = get_string_value(row,
"transfer_syntax");
1881 record.content_date = get_string_value(row,
"content_date");
1882 record.content_time = get_string_value(row,
"content_time");
1885 auto rows_it = row.find(
"rows");
1886 if (rows_it != row.end()) {
1887 if (std::holds_alternative<int64_t>(rows_it->second)) {
1888 record.rows =
static_cast<int>(std::get<int64_t>(rows_it->second));
1889 }
else if (std::holds_alternative<std::string>(rows_it->second)) {
1890 const auto& str = std::get<std::string>(rows_it->second);
1892 record.rows = std::stoi(str);
1897 auto cols_it = row.find(
"columns");
1898 if (cols_it != row.end()) {
1899 if (std::holds_alternative<int64_t>(cols_it->second)) {
1900 record.columns =
static_cast<int>(std::get<int64_t>(cols_it->second));
1901 }
else if (std::holds_alternative<std::string>(cols_it->second)) {
1902 const auto& str = std::get<std::string>(cols_it->second);
1904 record.columns = std::stoi(str);
1909 auto bits_it = row.find(
"bits_allocated");
1910 if (bits_it != row.end()) {
1911 if (std::holds_alternative<int64_t>(bits_it->second)) {
1912 record.bits_allocated =
static_cast<int>(std::get<int64_t>(bits_it->second));
1913 }
else if (std::holds_alternative<std::string>(bits_it->second)) {
1914 const auto& str = std::get<std::string>(bits_it->second);
1916 record.bits_allocated = std::stoi(str);
1921 auto frames_it = row.find(
"number_of_frames");
1922 if (frames_it != row.end()) {
1923 if (std::holds_alternative<int64_t>(frames_it->second)) {
1924 record.number_of_frames =
static_cast<int>(std::get<int64_t>(frames_it->second));
1925 }
else if (std::holds_alternative<std::string>(frames_it->second)) {
1926 const auto& str = std::get<std::string>(frames_it->second);
1928 record.number_of_frames = std::stoi(str);
1933 record.file_path = get_string_value(row,
"file_path");
1934 record.file_size = get_int64_value(row,
"file_size");
1935 record.file_hash = get_string_value(row,
"file_hash");
1937 auto created_str = get_string_value(row,
"created_at");
1938 record.created_at = parse_datetime(created_str.c_str());
Repository for audit log persistence.
std::shared_ptr< mpps_repository > mpps_repository_
auto find_patient(std::string_view patient_id) const -> std::optional< patient_record >
Find a patient by patient ID.
auto cleanup_old_audit_logs(std::chrono::hours age) -> Result< size_t >
Cleanup old audit log entries.
auto list_active_mpps(std::string_view station_ae) const -> Result< std::vector< mpps_record > >
List active (IN PROGRESS) MPPS records for a station.
auto find_patient_by_pk(int64_t pk) const -> std::optional< patient_record >
Find a patient by primary key.
auto verify_integrity() const -> VoidResult
Verify database integrity.
auto patient_count() const -> Result< size_t >
Get total patient count.
auto parse_series_row(void *stmt) const -> series_record
Parse a series record from a prepared statement.
auto vacuum() -> VoidResult
Reclaim unused space in the database.
auto delete_series(std::string_view series_uid) -> VoidResult
Delete a series by Series Instance UID.
auto get_ups_subscriptions(std::string_view subscriber_ae) const -> Result< std::vector< ups_subscription > >
Get all subscriptions for a subscriber.
index_database(const index_database &)=delete
auto find_instance_by_pk(int64_t pk) const -> std::optional< instance_record >
Find an instance by primary key.
auto find_ups_workitem(std::string_view workitem_uid) const -> std::optional< ups_workitem >
Find a UPS workitem by SOP Instance UID.
auto parse_study_row(void *stmt) const -> study_record
Parse a study record from a prepared statement.
auto find_study_by_pk(int64_t pk) const -> std::optional< study_record >
Find a study by primary key.
auto study_count() const -> Result< size_t >
Get total study count.
auto find_audit_by_pk(int64_t pk) const -> std::optional< audit_record >
Find an audit log entry by primary key.
auto list_instances(std::string_view series_uid) const -> Result< std::vector< instance_record > >
List all instances for a series.
auto search_ups_workitems(const ups_workitem_query &query) const -> Result< std::vector< ups_workitem > >
Search UPS workitems with query criteria.
auto search_mpps(const mpps_query &query) const -> Result< std::vector< mpps_record > >
Search MPPS records with query criteria.
auto get_ups_subscribers(std::string_view workitem_uid) const -> Result< std::vector< std::string > >
Get all subscribers for a workitem.
std::shared_ptr< audit_repository > audit_repository_
auto list_series(std::string_view study_uid) const -> Result< std::vector< series_record > >
List all series for a study.
std::shared_ptr< patient_repository > patient_repository_
Extracted repositories used by the facade API.
auto find_series_by_pk(int64_t pk) const -> std::optional< series_record >
Find a series by primary key.
auto query_audit_log(const audit_query &query) const -> Result< std::vector< audit_record > >
Query audit log entries.
std::shared_ptr< study_repository > study_repository_
auto find_mpps_by_study(std::string_view study_uid) const -> Result< std::vector< mpps_record > >
Find MPPS records by Study Instance UID.
std::shared_ptr< worklist_repository > worklist_repository_
auto find_worklist_item(std::string_view step_id, std::string_view accession_no) const -> std::optional< worklist_item >
Find a worklist item by step ID and accession number.
auto operator=(const index_database &) -> index_database &=delete
std::shared_ptr< ups_repository > ups_repository_
auto update_modalities_in_study(int64_t study_pk) -> VoidResult
Update modalities in study (denormalized field)
auto upsert_instance(int64_t series_pk, std::string_view sop_uid, std::string_view sop_class_uid, std::string_view file_path, int64_t file_size, std::string_view transfer_syntax="", std::optional< int > instance_number=std::nullopt) -> Result< int64_t >
Insert or update an instance record.
auto delete_ups_workitem(std::string_view workitem_uid) -> VoidResult
Delete a UPS workitem.
auto search_studies(const study_query &query) const -> Result< std::vector< study_record > >
Search studies with query criteria.
auto parse_ups_workitem_row(void *stmt) const -> ups_workitem
Parse a UPS workitem record from a prepared statement.
std::shared_ptr< instance_repository > instance_repository_
auto parse_mpps_row(void *stmt) const -> mpps_record
Parse an MPPS record from a prepared statement.
auto delete_worklist_item(std::string_view step_id, std::string_view accession_no) -> VoidResult
Delete a worklist item.
auto parse_instance_row(void *stmt) const -> instance_record
Parse an instance record from a prepared statement.
auto get_series_files(std::string_view series_instance_uid) const -> Result< std::vector< std::string > >
Get all file paths for a series.
auto parse_worklist_row(void *stmt) const -> worklist_item
Parse a worklist record from a prepared statement.
auto delete_patient(std::string_view patient_id) -> VoidResult
Delete a patient by patient ID.
auto series_count() const -> Result< size_t >
Get total series count.
auto native_handle() const noexcept -> sqlite3 *
Get the raw SQLite database handle.
auto create_mpps(std::string_view mpps_uid, std::string_view station_ae="", std::string_view modality="", std::string_view study_uid="", std::string_view accession_no="", std::string_view start_datetime="") -> Result< int64_t >
Create a new MPPS record (N-CREATE)
auto is_open() const noexcept -> bool
Check if the database is open.
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 >
Insert or update a patient record.
static auto open(std::string_view db_path) -> Result< std::unique_ptr< index_database > >
Open or create a database with default configuration.
auto parse_audit_row(void *stmt) const -> audit_record
Parse an audit record from a prepared statement.
auto ups_workitem_count() const -> Result< size_t >
Get total UPS workitem count.
auto subscribe_ups(const ups_subscription &subscription) -> Result< int64_t >
Subscribe to UPS workitem events.
auto add_audit_log(const audit_record &record) -> Result< int64_t >
Add a new audit log entry.
auto upsert_series(int64_t study_pk, std::string_view series_uid, std::string_view modality="", std::optional< int > series_number=std::nullopt, std::string_view series_description="", std::string_view body_part_examined="", std::string_view station_name="") -> Result< int64_t >
Insert or update a series record.
auto audit_count() const -> Result< size_t >
Get total audit log count.
auto update_worklist_status(std::string_view step_id, std::string_view accession_no, std::string_view new_status) -> VoidResult
Update worklist item status.
auto analyze() -> VoidResult
Update database statistics for query optimization.
auto cleanup_old_worklist_items(std::chrono::hours age) -> Result< size_t >
Cleanup old worklist items.
auto list_studies(std::string_view patient_id) const -> Result< std::vector< study_record > >
List all studies for a patient.
auto update_ups_workitem(const ups_workitem &workitem) -> VoidResult
Update an existing UPS workitem.
std::string path_
Database file path.
auto change_ups_state(std::string_view workitem_uid, std::string_view new_state, std::string_view transaction_uid="") -> VoidResult
Change UPS workitem state.
auto find_study(std::string_view study_uid) const -> std::optional< study_record >
Find a study by Study Instance UID.
auto update_mpps(std::string_view mpps_uid, std::string_view new_status, std::string_view end_datetime="", std::string_view performed_series="") -> VoidResult
Update an existing MPPS record (N-SET)
auto query_worklist(const worklist_query &query) const -> Result< std::vector< worklist_item > >
Query worklist items.
auto worklist_count() const -> Result< size_t >
Get total worklist count.
bool remove_on_close_
Remove the backing file on destruction for adapter-compatible memory DBs.
auto parse_patient_row(void *stmt) const -> patient_record
Parse a patient record from a prepared statement.
auto path() const -> std::string_view
Get the database file path.
auto delete_mpps(std::string_view mpps_uid) -> VoidResult
Delete an MPPS record.
auto search_instances(const instance_query &query) const -> Result< std::vector< instance_record > >
Search instances with query criteria.
auto unsubscribe_ups(std::string_view subscriber_ae, std::string_view workitem_uid="") -> VoidResult
Unsubscribe from UPS workitem events.
auto find_worklist_by_pk(int64_t pk) const -> std::optional< worklist_item >
Find a worklist item by primary key.
auto find_series(std::string_view series_uid) const -> std::optional< series_record >
Find a series by Series Instance UID.
auto create_ups_workitem(const ups_workitem &workitem) -> Result< int64_t >
Create a new UPS workitem (N-CREATE)
auto get_study_files(std::string_view study_instance_uid) const -> Result< std::vector< std::string > >
Get all file paths for a study.
auto instance_count() const -> Result< size_t >
Get total instance count.
migration_runner migration_runner_
Migration runner for schema management.
auto search_series(const series_query &query) const -> Result< std::vector< series_record > >
Search series with query criteria.
auto find_instance(std::string_view sop_uid) const -> std::optional< instance_record >
Find an instance by SOP Instance UID.
auto get_file_path(std::string_view sop_instance_uid) const -> Result< std::optional< std::string > >
Get file path for a SOP Instance UID.
auto delete_study(std::string_view study_uid) -> VoidResult
Delete a study by Study Instance UID.
auto checkpoint(bool truncate=false) -> VoidResult
Checkpoint WAL file.
auto schema_version() const -> int
Get the current schema version.
auto delete_instance(std::string_view sop_uid) -> VoidResult
Delete an instance by SOP Instance UID.
sqlite3 * db_
SQLite database handle (used for migrations and fallback)
auto cleanup_worklist_items_before(std::chrono::system_clock::time_point before) -> Result< size_t >
auto find_mpps_by_pk(int64_t pk) const -> std::optional< mpps_record >
Find an MPPS by primary key.
auto add_worklist_item(const worklist_item &item) -> Result< int64_t >
Add a new worklist item.
std::shared_ptr< series_repository > series_repository_
auto mpps_count() const -> Result< size_t >
Get total MPPS count.
auto search_patients(const patient_query &query) const -> Result< std::vector< patient_record > >
Search patients with query criteria.
static auto to_like_pattern(std::string_view pattern) -> std::string
Convert wildcard pattern to SQL LIKE pattern.
auto get_storage_stats() const -> Result< storage_stats >
Get storage statistics.
auto upsert_study(int64_t patient_pk, std::string_view study_uid, std::string_view study_id="", std::string_view study_date="", std::string_view study_time="", std::string_view accession_number="", std::string_view referring_physician="", std::string_view study_description="") -> Result< int64_t >
Insert or update a study record.
~index_database()
Destructor - closes database connection.
auto initialize_repositories() -> VoidResult
Initialize extracted repositories for facade delegation.
auto find_mpps(std::string_view mpps_uid) const -> std::optional< mpps_record >
Find an MPPS by SOP Instance UID.
auto get_current_version(sqlite3 *db) const -> int
Get the current schema version.
PACS index database for metadata storage and retrieval.
Repository for instance metadata persistence using base_repository pattern.
Repository for MPPS lifecycle persistence using base_repository pattern.
constexpr int database_query_error
constexpr int database_connection_error
@ counter
Monotonic increasing value.
@ record
RECORD - Treatment record dose.
Result< T > pacs_error(int code, const std::string &message, const std::string &details="")
Create a PACS error result with module context.
Repository for patient metadata persistence using base_repository pattern.
Result<T> type aliases and helpers for PACS system.
Repository for series metadata persistence using base_repository pattern.
Query parameters for audit log search.
Audit log record from the database.
Configuration for index database.
Storage statistics structure.
Instance record from the database.
Result of a migration operation.
MPPS record from the database.
Patient record from the database.
Series record from the database.
Study record from the database.
UPS subscription record from the database.
UPS workitem record from the database.
Worklist item record from the database.
Repository for study metadata persistence using base_repository pattern.
Compatibility header for cross-platform time functions.
Repository for UPS lifecycle and subscription persistence.
Repository for modality worklist persistence using base_repository pattern.