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

PACS index database manager. More...

#include <index_database.h>

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

Classes

struct  storage_stats
 Storage statistics structure. More...
 

Public Member Functions

 ~index_database ()
 Destructor - closes database connection.
 
 index_database (const index_database &)=delete
 
auto operator= (const index_database &) -> index_database &=delete
 
 index_database (index_database &&) noexcept
 
auto operator= (index_database &&) noexcept -> index_database &
 
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.
 
auto upsert_patient (const patient_record &record) -> Result< int64_t >
 Insert or update a patient record with full details.
 
auto find_patient (std::string_view patient_id) const -> std::optional< patient_record >
 Find a patient by patient ID.
 
auto find_patient_by_pk (int64_t pk) const -> std::optional< patient_record >
 Find a patient by primary key.
 
auto search_patients (const patient_query &query) const -> Result< std::vector< patient_record > >
 Search patients with query criteria.
 
auto delete_patient (std::string_view patient_id) -> VoidResult
 Delete a patient by patient ID.
 
auto patient_count () const -> Result< size_t >
 Get total patient count.
 
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.
 
auto upsert_study (const study_record &record) -> Result< int64_t >
 Insert or update a study record with full details.
 
auto find_study (std::string_view study_uid) const -> std::optional< study_record >
 Find a study by Study Instance UID.
 
auto find_study_by_pk (int64_t pk) const -> std::optional< study_record >
 Find a study by primary key.
 
auto list_studies (std::string_view patient_id) const -> Result< std::vector< study_record > >
 List all studies for a patient.
 
auto search_studies (const study_query &query) const -> Result< std::vector< study_record > >
 Search studies with query criteria.
 
auto delete_study (std::string_view study_uid) -> VoidResult
 Delete a study by Study Instance UID.
 
auto study_count () const -> Result< size_t >
 Get total study count.
 
auto study_count (std::string_view patient_id) const -> Result< size_t >
 Get study count for a specific patient.
 
auto update_modalities_in_study (int64_t study_pk) -> VoidResult
 Update modalities in study (denormalized field)
 
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 upsert_series (const series_record &record) -> Result< int64_t >
 Insert or update a series record with full details.
 
auto find_series (std::string_view series_uid) const -> std::optional< series_record >
 Find a series by Series Instance UID.
 
auto find_series_by_pk (int64_t pk) const -> std::optional< series_record >
 Find a series by primary key.
 
auto list_series (std::string_view study_uid) const -> Result< std::vector< series_record > >
 List all series for a study.
 
auto search_series (const series_query &query) const -> Result< std::vector< series_record > >
 Search series with query criteria.
 
auto delete_series (std::string_view series_uid) -> VoidResult
 Delete a series by Series Instance UID.
 
auto series_count () const -> Result< size_t >
 Get total series count.
 
auto series_count (std::string_view study_uid) const -> Result< size_t >
 Get series count for a specific study.
 
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 upsert_instance (const instance_record &record) -> Result< int64_t >
 Insert or update an instance record with full details.
 
auto find_instance (std::string_view sop_uid) const -> std::optional< instance_record >
 Find an instance by SOP Instance UID.
 
auto find_instance_by_pk (int64_t pk) const -> std::optional< instance_record >
 Find an instance 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_instances (const instance_query &query) const -> Result< std::vector< instance_record > >
 Search instances with query criteria.
 
auto delete_instance (std::string_view sop_uid) -> VoidResult
 Delete an instance by SOP Instance UID.
 
auto instance_count () const -> Result< size_t >
 Get total instance count.
 
auto instance_count (std::string_view series_uid) const -> Result< size_t >
 Get instance count for a specific series.
 
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 create_mpps (const mpps_record &record) -> Result< int64_t >
 Create a new MPPS record with full details.
 
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 update_mpps (const mpps_record &record) -> VoidResult
 Update an existing MPPS record with partial data.
 
auto find_mpps (std::string_view mpps_uid) const -> std::optional< mpps_record >
 Find an MPPS by SOP Instance UID.
 
auto find_mpps_by_pk (int64_t pk) const -> std::optional< mpps_record >
 Find an MPPS by primary key.
 
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_mpps_by_study (std::string_view study_uid) const -> Result< std::vector< mpps_record > >
 Find MPPS records by Study Instance UID.
 
auto search_mpps (const mpps_query &query) const -> Result< std::vector< mpps_record > >
 Search MPPS records with query criteria.
 
auto delete_mpps (std::string_view mpps_uid) -> VoidResult
 Delete an MPPS record.
 
auto mpps_count () const -> Result< size_t >
 Get total MPPS count.
 
auto mpps_count (std::string_view status) const -> Result< size_t >
 Get MPPS count by status.
 
auto add_worklist_item (const worklist_item &item) -> Result< int64_t >
 Add a new worklist item.
 
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 query_worklist (const worklist_query &query) const -> Result< std::vector< worklist_item > >
 Query worklist items.
 
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 find_worklist_by_pk (int64_t pk) const -> std::optional< worklist_item >
 Find a worklist item by primary key.
 
auto delete_worklist_item (std::string_view step_id, std::string_view accession_no) -> VoidResult
 Delete a worklist item.
 
auto cleanup_old_worklist_items (std::chrono::hours age) -> Result< size_t >
 Cleanup old worklist items.
 
auto cleanup_worklist_items_before (std::chrono::system_clock::time_point before) -> Result< size_t >
 Cleanup worklist items scheduled before a specific time.
 
auto worklist_count () const -> Result< size_t >
 Get total worklist count.
 
auto worklist_count (std::string_view status) const -> Result< size_t >
 Get worklist count by status.
 
auto create_ups_workitem (const ups_workitem &workitem) -> Result< int64_t >
 Create a new UPS workitem (N-CREATE)
 
auto update_ups_workitem (const ups_workitem &workitem) -> VoidResult
 Update an existing UPS workitem.
 
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_ups_workitem (std::string_view workitem_uid) const -> std::optional< ups_workitem >
 Find a UPS workitem by SOP Instance UID.
 
auto search_ups_workitems (const ups_workitem_query &query) const -> Result< std::vector< ups_workitem > >
 Search UPS workitems with query criteria.
 
auto delete_ups_workitem (std::string_view workitem_uid) -> VoidResult
 Delete a UPS workitem.
 
auto ups_workitem_count () const -> Result< size_t >
 Get total UPS workitem count.
 
auto ups_workitem_count (std::string_view state) const -> Result< size_t >
 Get UPS workitem count by state.
 
auto subscribe_ups (const ups_subscription &subscription) -> Result< int64_t >
 Subscribe to UPS workitem events.
 
auto unsubscribe_ups (std::string_view subscriber_ae, std::string_view workitem_uid="") -> VoidResult
 Unsubscribe from UPS workitem events.
 
auto get_ups_subscriptions (std::string_view subscriber_ae) const -> Result< std::vector< ups_subscription > >
 Get all subscriptions for a subscriber.
 
auto get_ups_subscribers (std::string_view workitem_uid) const -> Result< std::vector< std::string > >
 Get all subscribers for a workitem.
 
auto add_audit_log (const audit_record &record) -> Result< int64_t >
 Add a new audit log entry.
 
auto query_audit_log (const audit_query &query) const -> Result< std::vector< audit_record > >
 Query audit log entries.
 
auto find_audit_by_pk (int64_t pk) const -> std::optional< audit_record >
 Find an audit log entry by primary key.
 
auto audit_count () const -> Result< size_t >
 Get total audit log count.
 
auto cleanup_old_audit_logs (std::chrono::hours age) -> Result< size_t >
 Cleanup old audit log entries.
 
auto path () const -> std::string_view
 Get the database file path.
 
auto schema_version () const -> int
 Get the current schema version.
 
auto is_open () const noexcept -> bool
 Check if the database is open.
 
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 get_study_files (std::string_view study_instance_uid) const -> Result< std::vector< std::string > >
 Get all file paths for a study.
 
auto get_series_files (std::string_view series_instance_uid) const -> Result< std::vector< std::string > >
 Get all file paths for a series.
 
auto vacuum () -> VoidResult
 Reclaim unused space in the database.
 
auto analyze () -> VoidResult
 Update database statistics for query optimization.
 
auto verify_integrity () const -> VoidResult
 Verify database integrity.
 
auto native_handle () const noexcept -> sqlite3 *
 Get the raw SQLite database handle.
 
auto checkpoint (bool truncate=false) -> VoidResult
 Checkpoint WAL file.
 
auto get_storage_stats () const -> Result< storage_stats >
 Get storage statistics.
 

Static Public Member Functions

static auto open (std::string_view db_path) -> Result< std::unique_ptr< index_database > >
 Open or create a database with default configuration.
 
static auto open (std::string_view db_path, const index_config &config) -> Result< std::unique_ptr< index_database > >
 Open or create a database with custom configuration.
 

Private Member Functions

 index_database (sqlite3 *db, std::string path)
 Private constructor - use open() factory method.
 
auto parse_patient_row (void *stmt) const -> patient_record
 Parse a patient record from a prepared statement.
 
auto parse_study_row (void *stmt) const -> study_record
 Parse a study record from a prepared statement.
 
auto parse_series_row (void *stmt) const -> series_record
 Parse a series record from a prepared statement.
 
auto parse_instance_row (void *stmt) const -> instance_record
 Parse an instance record from a prepared statement.
 
auto parse_mpps_row (void *stmt) const -> mpps_record
 Parse an MPPS record from a prepared statement.
 
auto parse_worklist_row (void *stmt) const -> worklist_item
 Parse a worklist record from a prepared statement.
 
auto parse_ups_workitem_row (void *stmt) const -> ups_workitem
 Parse a UPS workitem record from a prepared statement.
 
auto parse_audit_row (void *stmt) const -> audit_record
 Parse an audit record from a prepared statement.
 
auto initialize_repositories () -> VoidResult
 Initialize extracted repositories for facade delegation.
 

Static Private Member Functions

static auto to_like_pattern (std::string_view pattern) -> std::string
 Convert wildcard pattern to SQL LIKE pattern.
 

Private Attributes

sqlite3 * db_ {nullptr}
 SQLite database handle (used for migrations and fallback)
 
std::string path_
 Database file path.
 
bool remove_on_close_ {false}
 Remove the backing file on destruction for adapter-compatible memory DBs.
 
std::shared_ptr< patient_repository > patient_repository_
 Extracted repositories used by the facade API.
 
std::shared_ptr< study_repository > study_repository_
 
std::shared_ptr< series_repository > series_repository_
 
std::shared_ptr< instance_repository > instance_repository_
 
std::shared_ptr< mpps_repository > mpps_repository_
 
std::shared_ptr< worklist_repository > worklist_repository_
 
std::shared_ptr< ups_repository > ups_repository_
 
std::shared_ptr< audit_repository > audit_repository_
 
migration_runner migration_runner_
 Migration runner for schema management.
 

Detailed Description

PACS index database manager.

Provides database operations for DICOM metadata storage and retrieval. Uses SQLite for persistence with automatic schema migration.

Thread Safety: This class is NOT thread-safe. External synchronization is required for concurrent access. Consider using a connection pool for multi-threaded applications.

Example:
// Open or create database
auto db_result = index_database::open(":memory:");
if (db_result.is_err()) {
// Handle error
}
auto db = std::move(db_result.value());
// Insert patient
auto pk = db->upsert_patient("12345", "Doe^John", "19800115", "M");
// Find patient
auto patient = db->find_patient("12345");
if (patient) {
std::cout << patient->patient_name << std::endl;
}
// Search with wildcards
patient_query query;
query.patient_name = "Doe*";
auto results = db->search_patients(query);
static auto open(std::string_view db_path) -> Result< std::unique_ptr< index_database > >
Open or create a database with default configuration.

Definition at line 123 of file index_database.h.

Constructor & Destructor Documentation

◆ ~index_database()

kcenon::pacs::storage::index_database::~index_database ( )

Destructor - closes database connection.

Definition at line 287 of file index_database.cpp.

287 {
288 patient_repository_.reset();
289 study_repository_.reset();
290 series_repository_.reset();
291 instance_repository_.reset();
292 mpps_repository_.reset();
293 worklist_repository_.reset();
294 ups_repository_.reset();
295 audit_repository_.reset();
296#ifdef PACS_WITH_DATABASE_SYSTEM
297 if (db_adapter_) {
298 (void)db_adapter_->disconnect();
299 db_adapter_.reset();
300 }
301#endif
302 if (db_) {
303 sqlite3_close(db_);
304 db_ = nullptr;
305 }
306 if (remove_on_close_) {
307 remove_database_sidecars(path_);
308 }
309}
std::shared_ptr< mpps_repository > mpps_repository_
std::shared_ptr< audit_repository > audit_repository_
std::shared_ptr< patient_repository > patient_repository_
Extracted repositories used by the facade API.
std::shared_ptr< study_repository > study_repository_
std::shared_ptr< worklist_repository > worklist_repository_
std::shared_ptr< ups_repository > ups_repository_
std::shared_ptr< instance_repository > instance_repository_
std::string path_
Database file path.
bool remove_on_close_
Remove the backing file on destruction for adapter-compatible memory DBs.
sqlite3 * db_
SQLite database handle (used for migrations and fallback)
std::shared_ptr< series_repository > series_repository_

References audit_repository_, db_, instance_repository_, mpps_repository_, path_, patient_repository_, remove_on_close_, series_repository_, study_repository_, ups_repository_, and worklist_repository_.

◆ index_database() [1/3]

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

◆ index_database() [2/3]

kcenon::pacs::storage::index_database::index_database ( index_database && other)
noexcept

Definition at line 688 of file index_database.cpp.

689 : db_(other.db_),
690 path_(std::move(other.path_)),
691 remove_on_close_(other.remove_on_close_),
692 patient_repository_(std::move(other.patient_repository_)),
693 study_repository_(std::move(other.study_repository_)),
694 series_repository_(std::move(other.series_repository_)),
695 instance_repository_(std::move(other.instance_repository_)),
696 mpps_repository_(std::move(other.mpps_repository_)),
697 worklist_repository_(std::move(other.worklist_repository_)),
698 ups_repository_(std::move(other.ups_repository_)),
699 audit_repository_(std::move(other.audit_repository_)) {
700#ifdef PACS_WITH_DATABASE_SYSTEM
701 db_adapter_ = std::move(other.db_adapter_);
702#endif
703 other.db_ = nullptr;
704 other.remove_on_close_ = false;
705}

◆ index_database() [3/3]

kcenon::pacs::storage::index_database::index_database ( sqlite3 * db,
std::string path )
explicitprivate

Private constructor - use open() factory method.

Definition at line 284 of file index_database.cpp.

285 : db_(db), path_(std::move(path)) {}
auto path() const -> std::string_view
Get the database file path.

Member Function Documentation

◆ add_audit_log()

auto kcenon::pacs::storage::index_database::add_audit_log ( const audit_record & record) -> Result<int64_t>
nodiscard

Add a new audit log entry.

Creates a new audit log record for HIPAA compliance and system monitoring.

Parameters
recordAudit record data (pk field is ignored)
Returns
Result containing the audit log primary key or error

Definition at line 1725 of file index_database.cpp.

1726 {
1727 return audit_repository_->add_audit_log(record);
1728}

◆ add_worklist_item()

auto kcenon::pacs::storage::index_database::add_worklist_item ( const worklist_item & item) -> Result<int64_t>
nodiscard

Add a new worklist item.

Creates a new scheduled procedure step entry for Modality Worklist. The step_status is set to 'SCHEDULED' by default.

Parameters
itemWorklist item data (pk field is ignored)
Returns
Result containing the worklist primary key or error

Definition at line 1533 of file index_database.cpp.

1534 {
1535 return worklist_repository_->add_worklist_item(item);
1536}

◆ analyze()

auto kcenon::pacs::storage::index_database::analyze ( ) -> VoidResult
nodiscard

Update database statistics for query optimization.

ANALYZE collects statistics about tables and indexes, which helps the query planner choose better execution plans. Should be run periodically, especially after bulk insertions.

Returns
VoidResult indicating success or error

Definition at line 1186 of file index_database.cpp.

1186 {
1187#ifdef PACS_WITH_DATABASE_SYSTEM
1188 // Prefer pacs_database_adapter for unified database operations (Issue #616)
1189 if (db_adapter_ && db_adapter_->is_connected()) {
1190 return db_adapter_->execute("ANALYZE;");
1191 }
1192#endif
1193
1194 auto rc = sqlite3_exec(db_, "ANALYZE;", nullptr, nullptr, nullptr);
1195 if (rc != SQLITE_OK) {
1196 return make_error<std::monostate>(
1197 rc, kcenon::pacs::compat::format("ANALYZE failed: {}", sqlite3_errmsg(db_)),
1198 "storage");
1199 }
1200 return ok();
1201}

◆ audit_count()

auto kcenon::pacs::storage::index_database::audit_count ( ) const -> Result<size_t>
nodiscard

Get total audit log count.

Returns
Result containing number of audit log entries in the database or error

Definition at line 1740 of file index_database.cpp.

1740 {
1741 return audit_repository_->audit_count();
1742}

References audit_repository_.

◆ change_ups_state()

auto kcenon::pacs::storage::index_database::change_ups_state ( std::string_view workitem_uid,
std::string_view new_state,
std::string_view transaction_uid = "" ) -> VoidResult
nodiscard

Change UPS workitem state.

Performs a state transition with validation per PS3.4 Table CC.1.1-2.

Parameters
workitem_uidThe workitem SOP Instance UID
new_stateThe target state
transaction_uidTransaction UID (required for IN PROGRESS transition)
Returns
VoidResult indicating success or error

Definition at line 1632 of file index_database.cpp.

1635 {
1636 return ups_repository_->change_ups_state(workitem_uid, new_state,
1637 transaction_uid);
1638}

◆ checkpoint()

auto kcenon::pacs::storage::index_database::checkpoint ( bool truncate = false) -> VoidResult
nodiscard

Checkpoint WAL file.

Forces a WAL checkpoint, writing all WAL content to the main database file. Useful for ensuring durability before backup.

Parameters
truncateIf true, truncate the WAL file after checkpoint
Returns
VoidResult indicating success or error

Definition at line 1252 of file index_database.cpp.

1252 {
1253 const char* sql =
1254 truncate ? "PRAGMA wal_checkpoint(TRUNCATE);"
1255 : "PRAGMA wal_checkpoint(PASSIVE);";
1256
1257#ifdef PACS_WITH_DATABASE_SYSTEM
1258 // Prefer pacs_database_adapter for unified database operations (Issue #616)
1259 if (db_adapter_ && db_adapter_->is_connected()) {
1260 return db_adapter_->execute(sql);
1261 }
1262#endif
1263
1264 auto rc = sqlite3_exec(db_, sql, nullptr, nullptr, nullptr);
1265 if (rc != SQLITE_OK) {
1266 return make_error<std::monostate>(
1267 rc, kcenon::pacs::compat::format("Checkpoint failed: {}", sqlite3_errmsg(db_)),
1268 "storage");
1269 }
1270 return ok();
1271}
auto truncate(std::string_view text, size_t max_length, std::string_view suffix) -> std::string
Truncate a string to a maximum length.

◆ cleanup_old_audit_logs()

auto kcenon::pacs::storage::index_database::cleanup_old_audit_logs ( std::chrono::hours age) -> Result<size_t>
nodiscard

Cleanup old audit log entries.

Removes audit log entries older than the specified age.

Parameters
ageMaximum age of entries to keep
Returns
Result containing the number of deleted entries or error

Definition at line 1744 of file index_database.cpp.

1745 {
1746 return audit_repository_->cleanup_old_audit_logs(age);
1747}

◆ cleanup_old_worklist_items()

auto kcenon::pacs::storage::index_database::cleanup_old_worklist_items ( std::chrono::hours age) -> Result<size_t>
nodiscard

Cleanup old worklist items.

Removes worklist items older than the specified age. Only deletes items that are not in SCHEDULED status.

Parameters
ageMaximum age of items to keep
Returns
Result containing the number of deleted items or error

Definition at line 1568 of file index_database.cpp.

1569 {
1570 return worklist_repository_->cleanup_old_worklist_items(age);
1571}

◆ cleanup_worklist_items_before()

auto kcenon::pacs::storage::index_database::cleanup_worklist_items_before ( std::chrono::system_clock::time_point before) -> Result<size_t>
nodiscard

Cleanup worklist items scheduled before a specific time.

Removes worklist items with scheduled_datetime before the specified time point. Only deletes items that are not in SCHEDULED status. This provides more precise control than the duration-based cleanup, eliminating timezone conversion ambiguities.

Parameters
beforeTime point; items scheduled before this are eligible for deletion
Returns
Result containing the number of deleted items or error
Example:
// Delete items scheduled before 2024-01-01 00:00:00 UTC
std::tm tm = {};
tm.tm_year = 2024 - 1900;
tm.tm_mon = 0;
tm.tm_mday = 1;
auto time = std::chrono::system_clock::from_time_t(std::mktime(&tm));
auto result = db->cleanup_worklist_items_before(time);

Definition at line 1573 of file index_database.cpp.

1574 {
1575 return worklist_repository_->cleanup_worklist_items_before(before);
1576}

◆ create_mpps() [1/2]

auto kcenon::pacs::storage::index_database::create_mpps ( const mpps_record & record) -> Result<int64_t>
nodiscard

Create a new MPPS record with full details.

Parameters
recordComplete MPPS record (pk field is ignored)
Returns
Result containing the MPPS primary key or error

Definition at line 1361 of file index_database.cpp.

1361 {
1362 return mpps_repository_->create_mpps(record);
1363}

◆ create_mpps() [2/2]

auto kcenon::pacs::storage::index_database::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>
nodiscard

Create a new MPPS record (N-CREATE)

Creates a new MPPS with status "IN PROGRESS". This corresponds to the DICOM N-CREATE operation received from modalities.

Parameters
mpps_uidSOP Instance UID for the MPPS (required, must be unique)
station_aePerforming station AE Title
modalityModality type (CT, MR, etc.)
study_uidRelated Study Instance UID
accession_noAccession number
start_datetimeProcedure step start date/time (YYYYMMDDHHMMSS)
Returns
Result containing the MPPS primary key or error

Definition at line 1349 of file index_database.cpp.

1355 {
1356 return mpps_repository_->create_mpps(mpps_uid, station_ae, modality,
1357 study_uid, accession_no,
1358 start_datetime);
1359}

◆ create_ups_workitem()

auto kcenon::pacs::storage::index_database::create_ups_workitem ( const ups_workitem & workitem) -> Result<int64_t>
nodiscard

Create a new UPS workitem (N-CREATE)

Creates a new UPS workitem with state "SCHEDULED". This corresponds to the DICOM N-CREATE operation.

Parameters
workitemThe workitem data (pk field is ignored)
Returns
Result containing the workitem primary key or error

Definition at line 1622 of file index_database.cpp.

1623 {
1624 return ups_repository_->create_ups_workitem(workitem);
1625}

◆ delete_instance()

auto kcenon::pacs::storage::index_database::delete_instance ( std::string_view sop_uid) -> VoidResult
nodiscard

Delete an instance by SOP Instance UID.

Parameters
sop_uidThe SOP Instance UID to delete
Returns
VoidResult indicating success or error

Definition at line 1087 of file index_database.cpp.

1087 {
1088 return instance_repository_->delete_instance(sop_uid);
1089}

◆ delete_mpps()

auto kcenon::pacs::storage::index_database::delete_mpps ( std::string_view mpps_uid) -> VoidResult
nodiscard

Delete an MPPS record.

Parameters
mpps_uidThe MPPS SOP Instance UID to delete
Returns
VoidResult indicating success or error

Definition at line 1403 of file index_database.cpp.

1403 {
1404 return mpps_repository_->delete_mpps(mpps_uid);
1405}

◆ delete_patient()

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

Delete a patient by patient ID.

This operation cascades to delete all related studies, series, and instances.

Parameters
patient_idThe patient ID to delete
Returns
VoidResult indicating success or error

Definition at line 779 of file index_database.cpp.

779 {
780 return patient_repository_->delete_patient(patient_id);
781}

◆ delete_series()

auto kcenon::pacs::storage::index_database::delete_series ( std::string_view series_uid) -> VoidResult
nodiscard

Delete a series by Series Instance UID.

This operation cascades to delete all related instances.

Parameters
series_uidThe Series Instance UID to delete
Returns
VoidResult indicating success or error

Definition at line 1000 of file index_database.cpp.

1000 {
1001 auto existing = find_series(series_uid);
1002 auto result = series_repository_->delete_series(series_uid);
1003 if (result.is_ok() && existing.has_value() && existing->study_pk > 0) {
1004 (void)update_modalities_in_study(existing->study_pk);
1005 }
1006 return result;
1007}
auto update_modalities_in_study(int64_t study_pk) -> VoidResult
Update modalities in study (denormalized field)
auto find_series(std::string_view series_uid) const -> std::optional< series_record >
Find a series by Series Instance UID.

◆ delete_study()

auto kcenon::pacs::storage::index_database::delete_study ( std::string_view study_uid) -> VoidResult
nodiscard

Delete a study by Study Instance UID.

This operation cascades to delete all related series and instances.

Parameters
study_uidThe Study Instance UID to delete
Returns
VoidResult indicating success or error

Definition at line 891 of file index_database.cpp.

891 {
892 return study_repository_->delete_study(study_uid);
893}

◆ delete_ups_workitem()

auto kcenon::pacs::storage::index_database::delete_ups_workitem ( std::string_view workitem_uid) -> VoidResult
nodiscard

Delete a UPS workitem.

Only COMPLETED or CANCELED workitems can be deleted unless no subscribers have a deletion lock.

Parameters
workitem_uidThe workitem SOP Instance UID
Returns
VoidResult indicating success or error

Definition at line 1650 of file index_database.cpp.

1651 {
1652 return ups_repository_->delete_ups_workitem(workitem_uid);
1653}

◆ delete_worklist_item()

auto kcenon::pacs::storage::index_database::delete_worklist_item ( std::string_view step_id,
std::string_view accession_no ) -> VoidResult
nodiscard

Delete a worklist item.

Parameters
step_idScheduled Procedure Step ID
accession_noAccession Number
Returns
VoidResult indicating success or error

Definition at line 1562 of file index_database.cpp.

1564 {
1565 return worklist_repository_->delete_worklist_item(step_id, accession_no);
1566}

◆ find_audit_by_pk()

auto kcenon::pacs::storage::index_database::find_audit_by_pk ( int64_t pk) const -> std::optional<audit_record>
nodiscard

Find an audit log entry by primary key.

Parameters
pkThe audit log primary key
Returns
Optional containing the audit record if found

Definition at line 1735 of file index_database.cpp.

1736 {
1737 return audit_repository_->find_audit_by_pk(pk);
1738}

◆ find_instance()

auto kcenon::pacs::storage::index_database::find_instance ( std::string_view sop_uid) const -> std::optional<instance_record>
nodiscard

Find an instance by SOP Instance UID.

Parameters
sop_uidThe SOP Instance UID to search for
Returns
Optional containing the instance record if found

Definition at line 1067 of file index_database.cpp.

1068 {
1069 return instance_repository_->find_instance(sop_uid);
1070}

◆ find_instance_by_pk()

auto kcenon::pacs::storage::index_database::find_instance_by_pk ( int64_t pk) const -> std::optional<instance_record>
nodiscard

Find an instance by primary key.

Parameters
pkThe instance's primary key
Returns
Optional containing the instance record if found

Definition at line 1072 of file index_database.cpp.

1073 {
1074 return instance_repository_->find_instance_by_pk(pk);
1075}

◆ find_mpps()

auto kcenon::pacs::storage::index_database::find_mpps ( std::string_view mpps_uid) const -> std::optional<mpps_record>
nodiscard

Find an MPPS by SOP Instance UID.

Parameters
mpps_uidThe MPPS SOP Instance UID to search for
Returns
Optional containing the MPPS record if found

Definition at line 1378 of file index_database.cpp.

1379 {
1380 return mpps_repository_->find_mpps(mpps_uid);
1381}

◆ find_mpps_by_pk()

auto kcenon::pacs::storage::index_database::find_mpps_by_pk ( int64_t pk) const -> std::optional<mpps_record>
nodiscard

Find an MPPS by primary key.

Parameters
pkThe MPPS primary key
Returns
Optional containing the MPPS record if found

Definition at line 1383 of file index_database.cpp.

1384 {
1385 return mpps_repository_->find_mpps_by_pk(pk);
1386}

◆ find_mpps_by_study()

auto kcenon::pacs::storage::index_database::find_mpps_by_study ( std::string_view study_uid) const -> Result<std::vector<mpps_record>>
nodiscard

Find MPPS records by Study Instance UID.

Parameters
study_uidThe Study Instance UID to search for
Returns
Result containing vector of MPPS records associated with the study or error

Definition at line 1393 of file index_database.cpp.

1394 {
1395 return mpps_repository_->find_mpps_by_study(study_uid);
1396}

◆ find_patient()

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

Find a patient by patient ID.

Parameters
patient_idThe patient ID to search for
Returns
Optional containing the patient record if found

Definition at line 764 of file index_database.cpp.

765 {
766 return patient_repository_->find_patient(patient_id);
767}

◆ find_patient_by_pk()

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

Find a patient by primary key.

Parameters
pkThe patient's primary key
Returns
Optional containing the patient record if found

Definition at line 769 of file index_database.cpp.

770 {
771 return patient_repository_->find_patient_by_pk(pk);
772}

◆ find_series()

auto kcenon::pacs::storage::index_database::find_series ( std::string_view series_uid) const -> std::optional<series_record>
nodiscard

Find a series by Series Instance UID.

Parameters
series_uidThe Series Instance UID to search for
Returns
Optional containing the series record if found

Definition at line 980 of file index_database.cpp.

981 {
982 return series_repository_->find_series(series_uid);
983}

◆ find_series_by_pk()

auto kcenon::pacs::storage::index_database::find_series_by_pk ( int64_t pk) const -> std::optional<series_record>
nodiscard

Find a series by primary key.

Parameters
pkThe series's primary key
Returns
Optional containing the series record if found

Definition at line 985 of file index_database.cpp.

986 {
987 return series_repository_->find_series_by_pk(pk);
988}

◆ find_study()

auto kcenon::pacs::storage::index_database::find_study ( std::string_view study_uid) const -> std::optional<study_record>
nodiscard

Find a study by Study Instance UID.

Parameters
study_uidThe Study Instance UID to search for
Returns
Optional containing the study record if found

Definition at line 869 of file index_database.cpp.

870 {
871 return study_repository_->find_study(study_uid);
872}

◆ find_study_by_pk()

auto kcenon::pacs::storage::index_database::find_study_by_pk ( int64_t pk) const -> std::optional<study_record>
nodiscard

Find a study by primary key.

Parameters
pkThe study's primary key
Returns
Optional containing the study record if found

Definition at line 874 of file index_database.cpp.

875 {
876 return study_repository_->find_study_by_pk(pk);
877}

◆ find_ups_workitem()

auto kcenon::pacs::storage::index_database::find_ups_workitem ( std::string_view workitem_uid) const -> std::optional<ups_workitem>
nodiscard

Find a UPS workitem by SOP Instance UID.

Parameters
workitem_uidThe workitem SOP Instance UID
Returns
Optional containing the workitem if found

Definition at line 1640 of file index_database.cpp.

1641 {
1642 return ups_repository_->find_ups_workitem(workitem_uid);
1643}

◆ find_worklist_by_pk()

auto kcenon::pacs::storage::index_database::find_worklist_by_pk ( int64_t pk) const -> std::optional<worklist_item>
nodiscard

Find a worklist item by primary key.

Parameters
pkThe worklist primary key
Returns
Optional containing the worklist item if found

Definition at line 1557 of file index_database.cpp.

1558 {
1559 return worklist_repository_->find_worklist_by_pk(pk);
1560}

◆ find_worklist_item()

auto kcenon::pacs::storage::index_database::find_worklist_item ( std::string_view step_id,
std::string_view accession_no ) const -> std::optional<worklist_item>
nodiscard

Find a worklist item by step ID and accession number.

Parameters
step_idScheduled Procedure Step ID
accession_noAccession Number
Returns
Optional containing the worklist item if found

Definition at line 1551 of file index_database.cpp.

1553 {
1554 return worklist_repository_->find_worklist_item(step_id, accession_no);
1555}

◆ get_file_path()

auto kcenon::pacs::storage::index_database::get_file_path ( std::string_view sop_instance_uid) const -> Result<std::optional<std::string>>
nodiscard

Get file path for a SOP Instance UID.

Convenience method to quickly look up the file path for a specific DICOM instance without loading the full record.

Parameters
sop_instance_uidThe SOP Instance UID to look up
Returns
Result containing optional file path (empty if not found) or error

Definition at line 1150 of file index_database.cpp.

1151 {
1152 return instance_repository_->get_file_path(sop_instance_uid);
1153}

◆ get_series_files()

auto kcenon::pacs::storage::index_database::get_series_files ( std::string_view series_instance_uid) const -> Result<std::vector<std::string>>
nodiscard

Get all file paths for a series.

Returns all DICOM file paths associated with a series.

Parameters
series_instance_uidThe Series Instance UID
Returns
Result containing vector of file paths for all instances in the series or error

Definition at line 1160 of file index_database.cpp.

1161 {
1162 return instance_repository_->get_series_files(series_instance_uid);
1163}

◆ get_storage_stats()

auto kcenon::pacs::storage::index_database::get_storage_stats ( ) const -> Result<storage_stats>
nodiscard

Get storage statistics.

Returns aggregate statistics about the database contents.

Returns
Result containing storage statistics structure or error

Definition at line 1288 of file index_database.cpp.

1288 {
1289 storage_stats stats;
1290
1291 auto patient_count_result = patient_count();
1292 if (patient_count_result.is_err()) {
1293 return Result<storage_stats>::err(patient_count_result.error());
1294 }
1295 stats.total_patients = patient_count_result.value();
1296
1297 auto study_count_result = study_count();
1298 if (study_count_result.is_err()) {
1299 return Result<storage_stats>::err(study_count_result.error());
1300 }
1301 stats.total_studies = study_count_result.value();
1302
1303 auto series_count_result = series_count();
1304 if (series_count_result.is_err()) {
1305 return Result<storage_stats>::err(series_count_result.error());
1306 }
1307 stats.total_series = series_count_result.value();
1308
1309 auto instance_count_result = instance_count();
1310 if (instance_count_result.is_err()) {
1311 return Result<storage_stats>::err(instance_count_result.error());
1312 }
1313 stats.total_instances = instance_count_result.value();
1314
1315 // Get total file size
1316 const char* file_size_sql =
1317 "SELECT COALESCE(SUM(file_size), 0) AS total_size FROM instances;";
1318
1319 sqlite3_stmt* stmt = nullptr;
1320 auto rc = sqlite3_prepare_v2(db_, file_size_sql, -1, &stmt, nullptr);
1321 if (rc != SQLITE_OK) {
1324 kcenon::pacs::compat::format("Failed to prepare query: {}",
1325 sqlite3_errmsg(db_)));
1326 }
1327
1328 if (sqlite3_step(stmt) == SQLITE_ROW) {
1329 stats.total_file_size = sqlite3_column_int64(stmt, 0);
1330 }
1331 sqlite3_finalize(stmt);
1332
1333 // Get database file size
1334 if (path_ != ":memory:") {
1335 std::error_code ec;
1336 auto size = std::filesystem::file_size(path_, ec);
1337 if (!ec) {
1338 stats.database_size = static_cast<int64_t>(size);
1339 }
1340 }
1341
1342 return ok(std::move(stats));
1343}
auto patient_count() const -> Result< size_t >
Get total patient count.
auto study_count() const -> Result< size_t >
Get total study count.
auto series_count() const -> Result< size_t >
Get total series count.
auto instance_count() const -> Result< size_t >
Get total instance count.
constexpr int database_query_error
Definition result.h:122
Result< T > pacs_error(int code, const std::string &message, const std::string &details="")
Create a PACS error result with module context.
Definition result.h:234

References kcenon::pacs::error_codes::database_query_error, kcenon::pacs::storage::index_database::storage_stats::database_size, db_, instance_count(), kcenon::pacs::pacs_error(), path_, patient_count(), series_count(), study_count(), kcenon::pacs::storage::index_database::storage_stats::total_file_size, kcenon::pacs::storage::index_database::storage_stats::total_instances, kcenon::pacs::storage::index_database::storage_stats::total_patients, kcenon::pacs::storage::index_database::storage_stats::total_series, and kcenon::pacs::storage::index_database::storage_stats::total_studies.

Here is the call graph for this function:

◆ get_study_files()

auto kcenon::pacs::storage::index_database::get_study_files ( std::string_view study_instance_uid) const -> Result<std::vector<std::string>>
nodiscard

Get all file paths for a study.

Returns all DICOM file paths associated with a study. Useful for bulk operations like C-MOVE or study export.

Parameters
study_instance_uidThe Study Instance UID
Returns
Result containing vector of file paths for all instances in the study or error

Definition at line 1155 of file index_database.cpp.

1156 {
1157 return instance_repository_->get_study_files(study_instance_uid);
1158}

◆ get_ups_subscribers()

auto kcenon::pacs::storage::index_database::get_ups_subscribers ( std::string_view workitem_uid) const -> Result<std::vector<std::string>>
nodiscard

Get all subscribers for a workitem.

Returns subscribers that would receive notifications for the specified workitem (workitem-specific + global subscriptions).

Parameters
workitem_uidThe workitem SOP Instance UID
Returns
Result containing vector of subscriber AE Titles or error

Definition at line 1684 of file index_database.cpp.

1685 {
1686 return ups_repository_->get_ups_subscribers(workitem_uid);
1687}

◆ get_ups_subscriptions()

auto kcenon::pacs::storage::index_database::get_ups_subscriptions ( std::string_view subscriber_ae) const -> Result<std::vector<ups_subscription>>
nodiscard

Get all subscriptions for a subscriber.

Parameters
subscriber_aeThe subscriber AE Title
Returns
Result containing vector of subscriptions or error

Definition at line 1679 of file index_database.cpp.

1680 {
1681 return ups_repository_->get_ups_subscriptions(subscriber_ae);
1682}

◆ initialize_repositories()

auto kcenon::pacs::storage::index_database::initialize_repositories ( ) -> VoidResult
nodiscardprivate

Initialize extracted repositories for facade delegation.

Definition at line 650 of file index_database.cpp.

650 {
651#ifdef PACS_WITH_DATABASE_SYSTEM
652 if (!db_adapter_ || !db_adapter_->is_connected()) {
653 return make_error<std::monostate>(
655 "PACS database adapter is not connected",
656 "storage");
657 }
658
659 patient_repository_ = std::make_shared<patient_repository>(db_adapter_);
660 study_repository_ = std::make_shared<study_repository>(db_adapter_);
661 series_repository_ = std::make_shared<series_repository>(db_adapter_);
662 instance_repository_ = std::make_shared<instance_repository>(db_adapter_);
663 mpps_repository_ = std::make_shared<mpps_repository>(db_adapter_);
664 worklist_repository_ = std::make_shared<worklist_repository>(db_adapter_);
665 ups_repository_ = std::make_shared<ups_repository>(db_adapter_);
666 audit_repository_ = std::make_shared<audit_repository>(db_adapter_);
667#else
668 if (db_ == nullptr) {
669 return make_error<std::monostate>(
671 "SQLite database handle is not available",
672 "storage");
673 }
674
675 patient_repository_ = std::make_shared<patient_repository>(db_);
676 study_repository_ = std::make_shared<study_repository>(db_);
677 series_repository_ = std::make_shared<series_repository>(db_);
678 instance_repository_ = std::make_shared<instance_repository>(db_);
679 mpps_repository_ = std::make_shared<mpps_repository>(db_);
680 worklist_repository_ = std::make_shared<worklist_repository>(db_);
681 ups_repository_ = std::make_shared<ups_repository>(db_);
682 audit_repository_ = std::make_shared<audit_repository>(db_);
683#endif
684
685 return ok();
686}
constexpr int database_connection_error
Definition result.h:121

References kcenon::pacs::error_codes::database_connection_error.

◆ instance_count() [1/2]

auto kcenon::pacs::storage::index_database::instance_count ( ) const -> Result<size_t>
nodiscard

Get total instance count.

Returns
Result containing number of instances in the database or error

Definition at line 1091 of file index_database.cpp.

1091 {
1092 return instance_repository_->instance_count();
1093}

References instance_repository_.

Referenced by get_storage_stats().

Here is the caller graph for this function:

◆ instance_count() [2/2]

auto kcenon::pacs::storage::index_database::instance_count ( std::string_view series_uid) const -> Result<size_t>
nodiscard

Get instance count for a specific series.

Parameters
series_uidThe Series Instance UID
Returns
Result containing number of instances for the series or error

Definition at line 1095 of file index_database.cpp.

1096 {
1097 return instance_repository_->instance_count(series_uid);
1098}

◆ is_open()

auto kcenon::pacs::storage::index_database::is_open ( ) const -> bool
nodiscardnoexcept

Check if the database is open.

Returns
true if the database connection is active

Definition at line 797 of file index_database.cpp.

797{ return db_ != nullptr; }

References db_.

◆ list_active_mpps()

auto kcenon::pacs::storage::index_database::list_active_mpps ( std::string_view station_ae) const -> Result<std::vector<mpps_record>>
nodiscard

List active (IN PROGRESS) MPPS records for a station.

Parameters
station_aeThe station AE Title to filter by
Returns
Result containing vector of active MPPS records or error

Definition at line 1388 of file index_database.cpp.

1389 {
1390 return mpps_repository_->list_active_mpps(station_ae);
1391}

◆ list_instances()

auto kcenon::pacs::storage::index_database::list_instances ( std::string_view series_uid) const -> Result<std::vector<instance_record>>
nodiscard

List all instances for a series.

Parameters
series_uidThe Series Instance UID to list instances for
Returns
Result containing vector of instance records for the series or error

Definition at line 1077 of file index_database.cpp.

1078 {
1079 return instance_repository_->list_instances(series_uid);
1080}

◆ list_series()

auto kcenon::pacs::storage::index_database::list_series ( std::string_view study_uid) const -> Result<std::vector<series_record>>
nodiscard

List all series for a study.

Parameters
study_uidThe Study Instance UID to list series for
Returns
Result containing vector of series records for the study or error

Definition at line 990 of file index_database.cpp.

991 {
992 return series_repository_->list_series(study_uid);
993}

◆ list_studies()

auto kcenon::pacs::storage::index_database::list_studies ( std::string_view patient_id) const -> Result<std::vector<study_record>>
nodiscard

List all studies for a patient.

Parameters
patient_idThe patient ID to list studies for
Returns
Result containing vector of study records for the patient or error

Definition at line 879 of file index_database.cpp.

880 {
881 study_query query;
882 query.patient_id = std::string(patient_id);
883 return study_repository_->search_studies(query);
884}
const atna_coded_value query
Query (110112)

◆ mpps_count() [1/2]

auto kcenon::pacs::storage::index_database::mpps_count ( ) const -> Result<size_t>
nodiscard

Get total MPPS count.

Returns
Result containing number of MPPS records in the database or error

Definition at line 1407 of file index_database.cpp.

1407 {
1408 return mpps_repository_->mpps_count();
1409}

References mpps_repository_.

◆ mpps_count() [2/2]

auto kcenon::pacs::storage::index_database::mpps_count ( std::string_view status) const -> Result<size_t>
nodiscard

Get MPPS count by status.

Parameters
statusThe status to count (IN PROGRESS, COMPLETED, DISCONTINUED)
Returns
Result containing number of MPPS records with the given status or error

Definition at line 1411 of file index_database.cpp.

1411 {
1412 return mpps_repository_->mpps_count(status);
1413}

◆ native_handle()

auto kcenon::pacs::storage::index_database::native_handle ( ) const -> sqlite3*
nodiscardnoexcept

Get the raw SQLite database handle.

Returns the underlying SQLite database handle for advanced operations such as creating cursors for streaming queries. The returned handle should not be closed or modified directly.

Warning
The returned handle is managed by this class. Do not close it.
Returns
Raw SQLite database handle

Definition at line 1273 of file index_database.cpp.

1273 {
1274 return db_;
1275}

References db_.

◆ open() [1/2]

auto kcenon::pacs::storage::index_database::open ( std::string_view db_path) -> Result<std::unique_ptr<index_database>>
staticnodiscard

Open or create a database with default configuration.

Opens an existing database or creates a new one at the specified path. Automatically runs pending migrations. Uses default configuration with WAL mode enabled.

Parameters
db_pathPath to the database file, or ":memory:" for in-memory DB
Returns
Result containing the database instance or error

Definition at line 156 of file index_database.cpp.

157 {
158 return open(db_path, index_config{});
159}

Referenced by kcenon::pacs::samples::mini_pacs::initialize_storage().

Here is the caller graph for this function:

◆ open() [2/2]

auto kcenon::pacs::storage::index_database::open ( std::string_view db_path,
const index_config & config ) -> Result<std::unique_ptr<index_database>>
staticnodiscard

Open or create a database with custom configuration.

Opens an existing database or creates a new one at the specified path. Automatically runs pending migrations.

Parameters
db_pathPath to the database file, or ":memory:" for in-memory DB. When compiled with PACS_WITH_DATABASE_SYSTEM, ":memory:" is silently redirected to a unique temporary file so the database_system SQLite backend can attach to it; WAL mode (if requested via config.wal_mode) is enabled on that rewritten path, and the temp file is removed on close.
configConfiguration options for database behavior
Returns
Result containing the database instance or error

Definition at line 161 of file index_database.cpp.

162 {
163 sqlite3* db = nullptr;
164
165 std::string effective_path;
166#ifdef PACS_WITH_DATABASE_SYSTEM
167 bool remove_on_close = false;
168 if (db_path == ":memory:") {
169 // database_system's SQLite backend expects a filesystem path, so use
170 // a temporary file that both sqlite3 and pacs_database_adapter can open.
171 effective_path = create_adapter_compatible_memory_path();
172 remove_on_close = true;
173 } else {
174 effective_path = std::string(db_path);
175 }
176#else
177 effective_path = std::string(db_path);
178#endif
179
180 auto rc = sqlite3_open_v2(effective_path.c_str(), &db,
181 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI,
182 nullptr);
183 if (rc != SQLITE_OK) {
184 std::string error_msg =
185 db ? sqlite3_errmsg(db) : "Failed to allocate memory";
186 if (db) {
187 sqlite3_close(db);
188 }
189 return make_error<std::unique_ptr<index_database>>(
190 rc, kcenon::pacs::compat::format("Failed to open database: {}", error_msg),
191 "storage");
192 }
193
194 // Enable foreign keys
195 rc = sqlite3_exec(db, "PRAGMA foreign_keys = ON;", nullptr, nullptr,
196 nullptr);
197 if (rc != SQLITE_OK) {
198 sqlite3_close(db);
199 return make_error<std::unique_ptr<index_database>>(
200 rc, "Failed to enable foreign keys", "storage");
201 }
202
203 // Configure WAL mode for better concurrency (except for in-memory DB).
204 // Gate on effective_path, not the caller-supplied db_path: under
205 // PACS_WITH_DATABASE_SYSTEM, ":memory:" is rewritten to a temp file so
206 // database_system's SQLite backend can attach, and that rewritten file
207 // both needs and benefits from WAL.
208 if (config.wal_mode && effective_path != ":memory:") {
209 rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", nullptr, nullptr,
210 nullptr);
211 if (rc != SQLITE_OK) {
212 sqlite3_close(db);
213 return make_error<std::unique_ptr<index_database>>(
214 rc, "Failed to enable WAL mode", "storage");
215 }
216 }
217
218 // Configure cache size (negative value means KB)
219 auto cache_sql =
220 kcenon::pacs::compat::format("PRAGMA cache_size = -{};", config.cache_size_mb * 1024);
221 rc = sqlite3_exec(db, cache_sql.c_str(), nullptr, nullptr, nullptr);
222 if (rc != SQLITE_OK) {
223 sqlite3_close(db);
224 return make_error<std::unique_ptr<index_database>>(
225 rc, "Failed to set cache size", "storage");
226 }
227
228 // Configure memory-mapped I/O. Use effective_path for the same reason
229 // as the WAL gate above.
230 if (config.mmap_enabled && effective_path != ":memory:") {
231 auto mmap_sql = kcenon::pacs::compat::format("PRAGMA mmap_size = {};", config.mmap_size);
232 rc = sqlite3_exec(db, mmap_sql.c_str(), nullptr, nullptr, nullptr);
233 if (rc != SQLITE_OK) {
234 // mmap failure is not critical, continue with regular I/O
235 }
236 }
237
238 // Set synchronous mode for durability with WAL
239 rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", nullptr, nullptr,
240 nullptr);
241 if (rc != SQLITE_OK) {
242 // Not critical, continue
243 }
244
245 // Create database instance
246 auto instance = std::unique_ptr<index_database>(
247 new index_database(db, effective_path));
248#ifdef PACS_WITH_DATABASE_SYSTEM
249 instance->remove_on_close_ = remove_on_close;
250#endif
251
252 // Run migrations
253 auto migration_result = instance->migration_runner_.run_migrations(db);
254 if (migration_result.is_err()) {
255 return make_error<std::unique_ptr<index_database>>(
256 migration_result.error().code,
257 kcenon::pacs::compat::format("Migration failed: {}",
258 migration_result.error().message),
259 "storage");
260 }
261
262#ifdef PACS_WITH_DATABASE_SYSTEM
263 // Initialize database_system for query building
264 // Support in-memory databases for testing (Issue #625)
265 auto db_init_result = instance->initialize_database_system();
266 if (db_init_result.is_err()) {
267 // unified_database_system may not have SQLite backend support in the
268 // current build, so keep the direct SQLite path available.
269 }
270#endif
271
272 auto repository_result = instance->initialize_repositories();
273 if (repository_result.is_err()) {
274 return make_error<std::unique_ptr<index_database>>(
275 repository_result.error().code,
276 kcenon::pacs::compat::format("Repository initialization failed: {}",
277 repository_result.error().message),
278 "storage");
279 }
280
281 return instance;
282}
index_database(const index_database &)=delete
std::string_view code

References code.

◆ operator=() [1/2]

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

◆ operator=() [2/2]

auto kcenon::pacs::storage::index_database::operator= ( index_database && other) -> index_database&
noexcept

Definition at line 707 of file index_database.cpp.

708 {
709 if (this != &other) {
710 patient_repository_.reset();
711 study_repository_.reset();
712 series_repository_.reset();
713 instance_repository_.reset();
714 mpps_repository_.reset();
715 worklist_repository_.reset();
716 ups_repository_.reset();
717 audit_repository_.reset();
718#ifdef PACS_WITH_DATABASE_SYSTEM
719 if (db_adapter_) {
720 (void)db_adapter_->disconnect();
721 }
722 db_adapter_ = std::move(other.db_adapter_);
723#endif
724 if (db_) {
725 sqlite3_close(db_);
726 }
727 if (remove_on_close_) {
728 remove_database_sidecars(path_);
729 }
730 db_ = other.db_;
731 path_ = std::move(other.path_);
732 remove_on_close_ = other.remove_on_close_;
733 patient_repository_ = std::move(other.patient_repository_);
734 study_repository_ = std::move(other.study_repository_);
735 series_repository_ = std::move(other.series_repository_);
736 instance_repository_ = std::move(other.instance_repository_);
737 mpps_repository_ = std::move(other.mpps_repository_);
738 worklist_repository_ = std::move(other.worklist_repository_);
739 ups_repository_ = std::move(other.ups_repository_);
740 audit_repository_ = std::move(other.audit_repository_);
741 other.db_ = nullptr;
742 other.remove_on_close_ = false;
743 }
744 return *this;
745}

◆ parse_audit_row()

auto kcenon::pacs::storage::index_database::parse_audit_row ( void * stmt) const -> audit_record
nodiscardprivate

Parse an audit record from a prepared statement.

Definition at line 1749 of file index_database.cpp.

1749 {
1750 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
1751 audit_record record;
1752
1753 record.pk = sqlite3_column_int64(stmt, 0);
1754 record.event_type = get_text(stmt, 1);
1755 record.outcome = get_text(stmt, 2);
1756
1757 auto timestamp_str = get_text(stmt, 3);
1758 record.timestamp = parse_datetime(timestamp_str.c_str());
1759
1760 record.user_id = get_text(stmt, 4);
1761 record.source_ae = get_text(stmt, 5);
1762 record.target_ae = get_text(stmt, 6);
1763 record.source_ip = get_text(stmt, 7);
1764 record.patient_id = get_text(stmt, 8);
1765 record.study_uid = get_text(stmt, 9);
1766 record.message = get_text(stmt, 10);
1767 record.details = get_text(stmt, 11);
1768
1769 return record;
1770}

◆ parse_instance_row()

auto kcenon::pacs::storage::index_database::parse_instance_row ( void * stmt) const -> instance_record
nodiscardprivate

Parse an instance record from a prepared statement.

Definition at line 1100 of file index_database.cpp.

1101 {
1102 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
1103 instance_record record;
1104
1105 record.pk = sqlite3_column_int64(stmt, 0);
1106 record.series_pk = sqlite3_column_int64(stmt, 1);
1107 record.sop_uid = get_text(stmt, 2);
1108 record.sop_class_uid = get_text(stmt, 3);
1109
1110 // Handle nullable instance_number
1111 if (sqlite3_column_type(stmt, 4) != SQLITE_NULL) {
1112 record.instance_number = sqlite3_column_int(stmt, 4);
1113 }
1114
1115 record.transfer_syntax = get_text(stmt, 5);
1116 record.content_date = get_text(stmt, 6);
1117 record.content_time = get_text(stmt, 7);
1118
1119 // Handle nullable image properties
1120 if (sqlite3_column_type(stmt, 8) != SQLITE_NULL) {
1121 record.rows = sqlite3_column_int(stmt, 8);
1122 }
1123
1124 if (sqlite3_column_type(stmt, 9) != SQLITE_NULL) {
1125 record.columns = sqlite3_column_int(stmt, 9);
1126 }
1127
1128 if (sqlite3_column_type(stmt, 10) != SQLITE_NULL) {
1129 record.bits_allocated = sqlite3_column_int(stmt, 10);
1130 }
1131
1132 if (sqlite3_column_type(stmt, 11) != SQLITE_NULL) {
1133 record.number_of_frames = sqlite3_column_int(stmt, 11);
1134 }
1135
1136 record.file_path = get_text(stmt, 12);
1137 record.file_size = sqlite3_column_int64(stmt, 13);
1138 record.file_hash = get_text(stmt, 14);
1139
1140 auto created_str = get_text(stmt, 15);
1141 record.created_at = parse_datetime(created_str.c_str());
1142
1143 return record;
1144}

◆ parse_mpps_row()

auto kcenon::pacs::storage::index_database::parse_mpps_row ( void * stmt) const -> mpps_record
nodiscardprivate

Parse an MPPS record from a prepared statement.

Definition at line 1415 of file index_database.cpp.

1415 {
1416 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
1417 mpps_record record;
1418
1419 record.pk = sqlite3_column_int64(stmt, 0);
1420 record.mpps_uid = get_text(stmt, 1);
1421 record.status = get_text(stmt, 2);
1422 record.start_datetime = get_text(stmt, 3);
1423 record.end_datetime = get_text(stmt, 4);
1424 record.station_ae = get_text(stmt, 5);
1425 record.station_name = get_text(stmt, 6);
1426 record.modality = get_text(stmt, 7);
1427 record.study_uid = get_text(stmt, 8);
1428 record.accession_no = get_text(stmt, 9);
1429 record.scheduled_step_id = get_text(stmt, 10);
1430 record.requested_proc_id = get_text(stmt, 11);
1431 record.performed_series = get_text(stmt, 12);
1432
1433 auto created_str = get_text(stmt, 13);
1434 record.created_at = parse_datetime(created_str.c_str());
1435
1436 auto updated_str = get_text(stmt, 14);
1437 record.updated_at = parse_datetime(updated_str.c_str());
1438
1439 return record;
1440}

◆ parse_patient_row()

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

Parse a patient record from a prepared statement.

Definition at line 803 of file index_database.cpp.

803 {
804 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
805 patient_record record;
806
807 record.pk = sqlite3_column_int64(stmt, 0);
808 record.patient_id = get_text(stmt, 1);
809 record.patient_name = get_text(stmt, 2);
810 record.birth_date = get_text(stmt, 3);
811 record.sex = get_text(stmt, 4);
812 record.other_ids = get_text(stmt, 5);
813 record.ethnic_group = get_text(stmt, 6);
814 record.comments = get_text(stmt, 7);
815
816 auto created_str = get_text(stmt, 8);
817 record.created_at = parse_datetime(created_str.c_str());
818
819 auto updated_str = get_text(stmt, 9);
820 record.updated_at = parse_datetime(updated_str.c_str());
821
822 return record;
823}

◆ parse_series_row()

auto kcenon::pacs::storage::index_database::parse_series_row ( void * stmt) const -> series_record
nodiscardprivate

Parse a series record from a prepared statement.

Definition at line 1017 of file index_database.cpp.

1017 {
1018 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
1019 series_record record;
1020
1021 record.pk = sqlite3_column_int64(stmt, 0);
1022 record.study_pk = sqlite3_column_int64(stmt, 1);
1023 record.series_uid = get_text(stmt, 2);
1024 record.modality = get_text(stmt, 3);
1025
1026 // Handle nullable series_number
1027 if (sqlite3_column_type(stmt, 4) != SQLITE_NULL) {
1028 record.series_number = sqlite3_column_int(stmt, 4);
1029 }
1030
1031 record.series_description = get_text(stmt, 5);
1032 record.body_part_examined = get_text(stmt, 6);
1033 record.station_name = get_text(stmt, 7);
1034 record.num_instances = sqlite3_column_int(stmt, 8);
1035
1036 auto created_str = get_text(stmt, 9);
1037 record.created_at = parse_datetime(created_str.c_str());
1038
1039 auto updated_str = get_text(stmt, 10);
1040 record.updated_at = parse_datetime(updated_str.c_str());
1041
1042 return record;
1043}

◆ parse_study_row()

auto kcenon::pacs::storage::index_database::parse_study_row ( void * stmt) const -> study_record
nodiscardprivate

Parse a study record from a prepared statement.

Definition at line 912 of file index_database.cpp.

912 {
913 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
914 study_record record;
915
916 record.pk = sqlite3_column_int64(stmt, 0);
917 record.patient_pk = sqlite3_column_int64(stmt, 1);
918 record.study_uid = get_text(stmt, 2);
919 record.study_id = get_text(stmt, 3);
920 record.study_date = get_text(stmt, 4);
921 record.study_time = get_text(stmt, 5);
922 record.accession_number = get_text(stmt, 6);
923 record.referring_physician = get_text(stmt, 7);
924 record.study_description = get_text(stmt, 8);
925 record.modalities_in_study = get_text(stmt, 9);
926 record.num_series = sqlite3_column_int(stmt, 10);
927 record.num_instances = sqlite3_column_int(stmt, 11);
928
929 auto created_str = get_text(stmt, 12);
930 record.created_at = parse_datetime(created_str.c_str());
931
932 auto updated_str = get_text(stmt, 13);
933 record.updated_at = parse_datetime(updated_str.c_str());
934
935 return record;
936}

◆ parse_ups_workitem_row()

auto kcenon::pacs::storage::index_database::parse_ups_workitem_row ( void * stmt) const -> ups_workitem
nodiscardprivate

Parse a UPS workitem record from a prepared statement.

Definition at line 1689 of file index_database.cpp.

1689 {
1690 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
1691 ups_workitem item;
1692
1693 item.pk = sqlite3_column_int64(stmt, 0);
1694 item.workitem_uid = get_text(stmt, 1);
1695 item.state = get_text(stmt, 2);
1696 item.procedure_step_label = get_text(stmt, 3);
1697 item.worklist_label = get_text(stmt, 4);
1698 item.priority = get_text(stmt, 5);
1699 item.scheduled_start_datetime = get_text(stmt, 6);
1700 item.expected_completion_datetime = get_text(stmt, 7);
1701 item.scheduled_station_name = get_text(stmt, 8);
1702 item.scheduled_station_class = get_text(stmt, 9);
1703 item.scheduled_station_geographic = get_text(stmt, 10);
1704 item.scheduled_human_performers = get_text(stmt, 11);
1705 item.input_information = get_text(stmt, 12);
1706 item.performing_ae = get_text(stmt, 13);
1707 item.progress_description = get_text(stmt, 14);
1708 item.progress_percent = sqlite3_column_int(stmt, 15);
1709 item.output_information = get_text(stmt, 16);
1710 item.transaction_uid = get_text(stmt, 17);
1711
1712 auto created_str = get_text(stmt, 18);
1713 item.created_at = parse_datetime(created_str.c_str());
1714
1715 auto updated_str = get_text(stmt, 19);
1716 item.updated_at = parse_datetime(updated_str.c_str());
1717
1718 return item;
1719}
constexpr dicom_tag item
Item.

◆ parse_worklist_row()

auto kcenon::pacs::storage::index_database::parse_worklist_row ( void * stmt) const -> worklist_item
nodiscardprivate

Parse a worklist record from a prepared statement.

Definition at line 1586 of file index_database.cpp.

1586 {
1587 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
1588 worklist_item item;
1589
1590 item.pk = sqlite3_column_int64(stmt, 0);
1591 item.step_id = get_text(stmt, 1);
1592 item.step_status = get_text(stmt, 2);
1593 item.patient_id = get_text(stmt, 3);
1594 item.patient_name = get_text(stmt, 4);
1595 item.birth_date = get_text(stmt, 5);
1596 item.sex = get_text(stmt, 6);
1597 item.accession_no = get_text(stmt, 7);
1598 item.requested_proc_id = get_text(stmt, 8);
1599 item.study_uid = get_text(stmt, 9);
1600 item.scheduled_datetime = get_text(stmt, 10);
1601 item.station_ae = get_text(stmt, 11);
1602 item.station_name = get_text(stmt, 12);
1603 item.modality = get_text(stmt, 13);
1604 item.procedure_desc = get_text(stmt, 14);
1605 item.protocol_code = get_text(stmt, 15);
1606 item.referring_phys = get_text(stmt, 16);
1607 item.referring_phys_id = get_text(stmt, 17);
1608
1609 auto created_str = get_text(stmt, 18);
1610 item.created_at = parse_datetime(created_str.c_str());
1611
1612 auto updated_str = get_text(stmt, 19);
1613 item.updated_at = parse_datetime(updated_str.c_str());
1614
1615 return item;
1616}

◆ path()

auto kcenon::pacs::storage::index_database::path ( ) const -> std::string_view
nodiscard

Get the database file path.

Returns
Path to the database file

Definition at line 791 of file index_database.cpp.

791{ return path_; }

References path_.

◆ patient_count()

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

Get total patient count.

Returns
Result containing number of patients in the database or error

Definition at line 783 of file index_database.cpp.

783 {
784 return patient_repository_->patient_count();
785}

References patient_repository_.

Referenced by get_storage_stats().

Here is the caller graph for this function:

◆ query_audit_log()

auto kcenon::pacs::storage::index_database::query_audit_log ( const audit_query & query) const -> Result<std::vector<audit_record>>
nodiscard

Query audit log entries.

Returns audit log entries matching the query criteria.

Parameters
queryQuery parameters with optional filters
Returns
Result containing vector of matching audit records or error

Definition at line 1730 of file index_database.cpp.

1731 {
1732 return audit_repository_->query_audit_log(query);
1733}

◆ query_worklist()

auto kcenon::pacs::storage::index_database::query_worklist ( const worklist_query & query) const -> Result<std::vector<worklist_item>>
nodiscard

Query worklist items.

Returns worklist items matching the query criteria. By default, only returns items with status 'SCHEDULED'. Used for MWL C-FIND operations.

Parameters
queryQuery parameters with optional filters
Returns
Result containing vector of matching worklist items or error

Definition at line 1546 of file index_database.cpp.

1547 {
1548 return worklist_repository_->query_worklist(query);
1549}

◆ schema_version()

auto kcenon::pacs::storage::index_database::schema_version ( ) const -> int
nodiscard

Get the current schema version.

Returns
Current schema version number

Definition at line 793 of file index_database.cpp.

793 {
795}
migration_runner migration_runner_
Migration runner for schema management.
auto get_current_version(sqlite3 *db) const -> int
Get the current schema version.

References db_, kcenon::pacs::storage::migration_runner::get_current_version(), and migration_runner_.

Here is the call graph for this function:

◆ search_instances()

auto kcenon::pacs::storage::index_database::search_instances ( const instance_query & query) const -> Result<std::vector<instance_record>>
nodiscard

Search instances with query criteria.

Can filter by series UID, SOP class, and other attributes.

Parameters
queryQuery parameters with optional filters
Returns
Result containing vector of matching instance records or error

Definition at line 1082 of file index_database.cpp.

1083 {
1084 return instance_repository_->search_instances(query);
1085}

◆ search_mpps()

auto kcenon::pacs::storage::index_database::search_mpps ( const mpps_query & query) const -> Result<std::vector<mpps_record>>
nodiscard

Search MPPS records with query criteria.

Parameters
queryQuery parameters with optional filters
Returns
Result containing vector of matching MPPS records or error

Definition at line 1398 of file index_database.cpp.

1399 {
1400 return mpps_repository_->search_mpps(query);
1401}

◆ search_patients()

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

Search patients with query criteria.

Supports wildcard matching using '' character.

  • "Doe</em>" matches names starting with "Doe"
  • "*John" matches names ending with "John"
  • "*oh*" matches names containing "oh"

Parameters
queryQuery parameters with optional filters
Returns
Result containing vector of matching patient records or error

Definition at line 774 of file index_database.cpp.

775 {
776 return patient_repository_->search_patients(query);
777}

◆ search_series()

auto kcenon::pacs::storage::index_database::search_series ( const series_query & query) const -> Result<std::vector<series_record>>
nodiscard

Search series with query criteria.

Supports wildcard matching using '*' character. Can filter by study UID, modality, and other attributes.

Parameters
queryQuery parameters with optional filters
Returns
Result containing vector of matching series records or error

Definition at line 995 of file index_database.cpp.

996 {
997 return series_repository_->search_series(query);
998}

◆ search_studies()

auto kcenon::pacs::storage::index_database::search_studies ( const study_query & query) const -> Result<std::vector<study_record>>
nodiscard

Search studies with query criteria.

Supports wildcard matching using '*' character. Can filter by patient attributes, study attributes, and date ranges.

Parameters
queryQuery parameters with optional filters
Returns
Result containing vector of matching study records or error

Definition at line 886 of file index_database.cpp.

887 {
888 return study_repository_->search_studies(query);
889}

◆ search_ups_workitems()

auto kcenon::pacs::storage::index_database::search_ups_workitems ( const ups_workitem_query & query) const -> Result<std::vector<ups_workitem>>
nodiscard

Search UPS workitems with query criteria.

Used for UPS C-FIND operations.

Parameters
queryQuery parameters with optional filters
Returns
Result containing vector of matching workitems or error

Definition at line 1645 of file index_database.cpp.

1646 {
1647 return ups_repository_->search_ups_workitems(query);
1648}

◆ series_count() [1/2]

auto kcenon::pacs::storage::index_database::series_count ( ) const -> Result<size_t>
nodiscard

Get total series count.

Returns
Result containing number of series in the database or error

Definition at line 1009 of file index_database.cpp.

1009 {
1010 return series_repository_->series_count();
1011}

References series_repository_.

Referenced by get_storage_stats().

Here is the caller graph for this function:

◆ series_count() [2/2]

auto kcenon::pacs::storage::index_database::series_count ( std::string_view study_uid) const -> Result<size_t>
nodiscard

Get series count for a specific study.

Parameters
study_uidThe Study Instance UID
Returns
Result containing number of series for the study or error

Definition at line 1013 of file index_database.cpp.

1013 {
1014 return series_repository_->series_count(study_uid);
1015}

◆ study_count() [1/2]

auto kcenon::pacs::storage::index_database::study_count ( ) const -> Result<size_t>
nodiscard

Get total study count.

Returns
Result containing number of studies in the database or error

Definition at line 895 of file index_database.cpp.

895 {
896 return study_repository_->study_count();
897}

References study_repository_.

Referenced by get_storage_stats().

Here is the caller graph for this function:

◆ study_count() [2/2]

auto kcenon::pacs::storage::index_database::study_count ( std::string_view patient_id) const -> Result<size_t>
nodiscard

Get study count for a specific patient.

Parameters
patient_idThe patient ID
Returns
Result containing number of studies for the patient or error

Definition at line 899 of file index_database.cpp.

899 {
900 auto patient = patient_repository_->find_patient(patient_id);
901 if (!patient.has_value()) {
902 return ok(static_cast<size_t>(0));
903 }
904 return study_repository_->study_count_for_patient(patient->pk);
905}

◆ subscribe_ups()

auto kcenon::pacs::storage::index_database::subscribe_ups ( const ups_subscription & subscription) -> Result<int64_t>
nodiscard

Subscribe to UPS workitem events.

Parameters
subscriptionThe subscription data (pk field is ignored)
Returns
Result containing the subscription primary key or error

Definition at line 1668 of file index_database.cpp.

1669 {
1670 return ups_repository_->subscribe_ups(subscription);
1671}

◆ to_like_pattern()

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

Convert wildcard pattern to SQL LIKE pattern.

Converts '*' to '' for SQL LIKE matching

Definition at line 825 of file index_database.cpp.

825 {
826 std::string result;
827 result.reserve(pattern.size());
828
829 for (char c : pattern) {
830 if (c == '*') {
831 result += '%';
832 } else if (c == '?') {
833 result += '_';
834 } else if (c == '%' || c == '_') {
835 // Escape SQL wildcards
836 result += '\\';
837 result += c;
838 } else {
839 result += c;
840 }
841 }
842
843 return result;
844}

◆ unsubscribe_ups()

auto kcenon::pacs::storage::index_database::unsubscribe_ups ( std::string_view subscriber_ae,
std::string_view workitem_uid = "" ) -> VoidResult
nodiscard

Unsubscribe from UPS workitem events.

Parameters
subscriber_aeThe subscriber AE Title
workitem_uidThe workitem UID (empty for global unsubscribe)
Returns
VoidResult indicating success or error

Definition at line 1673 of file index_database.cpp.

1675 {
1676 return ups_repository_->unsubscribe_ups(subscriber_ae, workitem_uid);
1677}

◆ update_modalities_in_study()

auto kcenon::pacs::storage::index_database::update_modalities_in_study ( int64_t study_pk) -> VoidResult
nodiscard

Update modalities in study (denormalized field)

Called after series insert/delete to update the modalities_in_study field.

Parameters
study_pkThe study's primary key
Returns
VoidResult indicating success or error

Definition at line 907 of file index_database.cpp.

908 {
909 return study_repository_->update_modalities_in_study(study_pk);
910}

◆ update_mpps() [1/2]

auto kcenon::pacs::storage::index_database::update_mpps ( const mpps_record & record) -> VoidResult
nodiscard

Update an existing MPPS record with partial data.

Only non-empty fields in the record will be updated.

Parameters
recordMPPS record with fields to update (mpps_uid required)
Returns
VoidResult indicating success or error

Definition at line 1374 of file index_database.cpp.

1374 {
1375 return mpps_repository_->update_mpps(record);
1376}

◆ update_mpps() [2/2]

auto kcenon::pacs::storage::index_database::update_mpps ( std::string_view mpps_uid,
std::string_view new_status,
std::string_view end_datetime = "",
std::string_view performed_series = "" ) -> VoidResult
nodiscard

Update an existing MPPS record (N-SET)

Updates the MPPS status and attributes. This corresponds to the DICOM N-SET operation. Status transitions are validated:

  • IN PROGRESS -> COMPLETED or DISCONTINUED (allowed)
  • COMPLETED or DISCONTINUED -> any (not allowed, final states)
Parameters
mpps_uidSOP Instance UID of the MPPS to update
new_statusNew status (COMPLETED or DISCONTINUED)
end_datetimeProcedure step end date/time
performed_seriesJSON string of performed series information
Returns
VoidResult indicating success or error

Definition at line 1365 of file index_database.cpp.

1369 {
1370 return mpps_repository_->update_mpps(mpps_uid, new_status, end_datetime,
1371 performed_series);
1372}

◆ update_ups_workitem()

auto kcenon::pacs::storage::index_database::update_ups_workitem ( const ups_workitem & workitem) -> VoidResult
nodiscard

Update an existing UPS workitem.

Updates the workitem attributes. State transitions are validated:

  • SCHEDULED -> IN PROGRESS or CANCELED (allowed)
  • IN PROGRESS -> COMPLETED or CANCELED (allowed)
  • COMPLETED or CANCELED -> any (not allowed, final states)
Parameters
workitemUpdated workitem data (workitem_uid required)
Returns
VoidResult indicating success or error

Definition at line 1627 of file index_database.cpp.

1628 {
1629 return ups_repository_->update_ups_workitem(workitem);
1630}

◆ update_worklist_status()

auto kcenon::pacs::storage::index_database::update_worklist_status ( std::string_view step_id,
std::string_view accession_no,
std::string_view new_status ) -> VoidResult
nodiscard

Update worklist item status.

Called when MPPS is received to update the corresponding worklist item status from SCHEDULED to STARTED or COMPLETED.

Parameters
step_idScheduled Procedure Step ID
accession_noAccession Number
new_statusNew status (STARTED or COMPLETED)
Returns
VoidResult indicating success or error

Definition at line 1538 of file index_database.cpp.

1541 {
1542 return worklist_repository_->update_worklist_status(step_id, accession_no,
1543 new_status);
1544}

◆ ups_workitem_count() [1/2]

auto kcenon::pacs::storage::index_database::ups_workitem_count ( ) const -> Result<size_t>
nodiscard

Get total UPS workitem count.

Returns
Result containing number of workitems in the database or error

Definition at line 1655 of file index_database.cpp.

1655 {
1656 return ups_repository_->ups_workitem_count();
1657}

References ups_repository_.

◆ ups_workitem_count() [2/2]

auto kcenon::pacs::storage::index_database::ups_workitem_count ( std::string_view state) const -> Result<size_t>
nodiscard

Get UPS workitem count by state.

Parameters
stateThe state to count
Returns
Result containing number of workitems with the given state or error

Definition at line 1659 of file index_database.cpp.

1660 {
1661 return ups_repository_->ups_workitem_count(state);
1662}

◆ upsert_instance() [1/2]

auto kcenon::pacs::storage::index_database::upsert_instance ( const instance_record & record) -> Result<int64_t>
nodiscard

Insert or update an instance record with full details.

Parameters
recordComplete instance record (pk field is ignored for insert)
Returns
Result containing the instance's primary key or error

Definition at line 1062 of file index_database.cpp.

1063 {
1064 return instance_repository_->upsert_instance(record);
1065}

◆ upsert_instance() [2/2]

auto kcenon::pacs::storage::index_database::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>
nodiscard

Insert or update an instance record.

If an instance with the same sop_uid exists, updates the record. Otherwise, inserts a new record.

Parameters
series_pkForeign key to the series (required)
sop_uidSOP Instance UID (required, max 64 chars)
sop_class_uidSOP Class UID (required)
file_pathPath to the stored DICOM file (required)
file_sizeSize of the file in bytes (required, >= 0)
transfer_syntaxTransfer Syntax UID
instance_numberInstance number
Returns
Result containing the instance's primary key or error

Definition at line 1049 of file index_database.cpp.

1056 {
1057 return instance_repository_->upsert_instance(
1058 series_pk, sop_uid, sop_class_uid, file_path, file_size,
1059 transfer_syntax, instance_number);
1060}

◆ upsert_patient() [1/2]

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

Insert or update a patient record with full details.

Parameters
recordComplete patient record (pk field is ignored for insert)
Returns
Result containing the patient's primary key or error

Definition at line 759 of file index_database.cpp.

760 {
761 return patient_repository_->upsert_patient(record);
762}

◆ upsert_patient() [2/2]

auto kcenon::pacs::storage::index_database::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

Insert or update a patient record.

If a patient with the same patient_id exists, updates the record. Otherwise, inserts a new record.

Parameters
patient_idPatient ID (required, max 64 chars)
patient_namePatient's name in DICOM PN format
birth_dateBirth date in YYYYMMDD format
sexSex code (M, F, O)
Returns
Result containing the patient's primary key or error

Definition at line 751 of file index_database.cpp.

754 {
755 return patient_repository_->upsert_patient(patient_id, patient_name,
756 birth_date, sex);
757}

◆ upsert_series() [1/2]

auto kcenon::pacs::storage::index_database::upsert_series ( const series_record & record) -> Result<int64_t>
nodiscard

Insert or update a series record with full details.

Parameters
recordComplete series record (pk field is ignored for insert)
Returns
Result containing the series's primary key or error

Definition at line 961 of file index_database.cpp.

962 {
963 auto existing = find_series(record.series_uid);
964 auto result = series_repository_->upsert_series(record);
965 if (result.is_err()) {
966 return result;
967 }
968
969 if (existing.has_value() && existing->study_pk > 0 &&
970 existing->study_pk != record.study_pk) {
971 (void)update_modalities_in_study(existing->study_pk);
972 }
973 if (record.study_pk > 0) {
974 (void)update_modalities_in_study(record.study_pk);
975 }
976
977 return result;
978}

◆ upsert_series() [2/2]

auto kcenon::pacs::storage::index_database::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>
nodiscard

Insert or update a series record.

If a series with the same series_uid exists, updates the record. Otherwise, inserts a new record.

Parameters
study_pkForeign key to the study (required)
series_uidSeries Instance UID (required, max 64 chars)
modalityModality (e.g., CT, MR)
series_numberSeries number
series_descriptionSeries description
body_part_examinedBody part examined
station_nameStation name
Returns
Result containing the series's primary key or error

Definition at line 942 of file index_database.cpp.

949 {
950 series_record record;
951 record.study_pk = study_pk;
952 record.series_uid = std::string(series_uid);
953 record.modality = std::string(modality);
954 record.series_number = series_number;
955 record.series_description = std::string(series_description);
956 record.body_part_examined = std::string(body_part_examined);
957 record.station_name = std::string(station_name);
958 return upsert_series(record);
959}
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.
constexpr dicom_tag series_number
Series Number.

◆ upsert_study() [1/2]

auto kcenon::pacs::storage::index_database::upsert_study ( const study_record & record) -> Result<int64_t>
nodiscard

Insert or update a study record with full details.

Parameters
recordComplete study record (pk field is ignored for insert)
Returns
Result containing the study's primary key or error

Definition at line 864 of file index_database.cpp.

865 {
866 return study_repository_->upsert_study(record);
867}

◆ upsert_study() [2/2]

auto kcenon::pacs::storage::index_database::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>
nodiscard

Insert or update a study record.

If a study with the same study_uid exists, updates the record. Otherwise, inserts a new record.

Parameters
patient_pkForeign key to the patient (required)
study_uidStudy Instance UID (required, max 64 chars)
study_idStudy ID
study_dateStudy date in YYYYMMDD format
study_timeStudy time in HHMMSS format
accession_numberAccession number
referring_physicianReferring physician's name
study_descriptionStudy description
Returns
Result containing the study's primary key or error

Definition at line 850 of file index_database.cpp.

858 {
859 return study_repository_->upsert_study(
860 patient_pk, study_uid, study_id, study_date, study_time,
861 accession_number, referring_physician, study_description);
862}

◆ vacuum()

auto kcenon::pacs::storage::index_database::vacuum ( ) -> VoidResult
nodiscard

Reclaim unused space in the database.

VACUUM rebuilds the database file, repacking it into a minimal amount of disk space. This can reduce file size after large deletions but may take time for large databases.

Note: This operation requires exclusive access and may take significant time for large databases.

Returns
VoidResult indicating success or error

Definition at line 1169 of file index_database.cpp.

1169 {
1170#ifdef PACS_WITH_DATABASE_SYSTEM
1171 // Prefer pacs_database_adapter for unified database operations (Issue #616)
1172 if (db_adapter_ && db_adapter_->is_connected()) {
1173 return db_adapter_->execute("VACUUM;");
1174 }
1175#endif
1176
1177 auto rc = sqlite3_exec(db_, "VACUUM;", nullptr, nullptr, nullptr);
1178 if (rc != SQLITE_OK) {
1179 return make_error<std::monostate>(
1180 rc, kcenon::pacs::compat::format("VACUUM failed: {}", sqlite3_errmsg(db_)),
1181 "storage");
1182 }
1183 return ok();
1184}

◆ verify_integrity()

auto kcenon::pacs::storage::index_database::verify_integrity ( ) const -> VoidResult
nodiscard

Verify database integrity.

Runs SQLite's integrity check to verify the database structure and detect any corruption issues.

Returns
VoidResult indicating success or error with details

Definition at line 1203 of file index_database.cpp.

1203 {
1204#ifdef PACS_WITH_DATABASE_SYSTEM
1205 // Prefer pacs_database_adapter for unified database operations (Issue #616)
1206 if (db_adapter_ && db_adapter_->is_connected()) {
1207 auto result = db_adapter_->select("PRAGMA integrity_check;");
1208 if (result.is_err()) {
1209 return make_error<std::monostate>(
1210 result.error().code, result.error().message, "storage");
1211 }
1212
1213 for (const auto& row : result.value()) {
1214 auto it = row.find("integrity_check");
1215 if (it != row.end() && it->second != "ok") {
1216 return make_error<std::monostate>(
1217 -1, kcenon::pacs::compat::format("Integrity check failed: {}", it->second),
1218 "storage");
1219 }
1220 }
1221 return ok();
1222 }
1223#endif
1224
1225 const char* sql = "PRAGMA integrity_check;";
1226
1227 sqlite3_stmt* stmt = nullptr;
1228 auto rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
1229 if (rc != SQLITE_OK) {
1230 return make_error<std::monostate>(
1231 rc,
1232 kcenon::pacs::compat::format("Failed to prepare integrity check: {}",
1233 sqlite3_errmsg(db_)),
1234 "storage");
1235 }
1236
1237 std::string result;
1238 while (sqlite3_step(stmt) == SQLITE_ROW) {
1239 result = get_text(stmt, 0);
1240 if (result != "ok") {
1241 sqlite3_finalize(stmt);
1242 return make_error<std::monostate>(
1243 -1, kcenon::pacs::compat::format("Integrity check failed: {}", result),
1244 "storage");
1245 }
1246 }
1247
1248 sqlite3_finalize(stmt);
1249 return ok();
1250}

References db_.

◆ worklist_count() [1/2]

auto kcenon::pacs::storage::index_database::worklist_count ( ) const -> Result<size_t>
nodiscard

Get total worklist count.

Returns
Result containing number of worklist items in the database or error

Definition at line 1578 of file index_database.cpp.

1578 {
1579 return worklist_repository_->worklist_count();
1580}

References worklist_repository_.

◆ worklist_count() [2/2]

auto kcenon::pacs::storage::index_database::worklist_count ( std::string_view status) const -> Result<size_t>
nodiscard

Get worklist count by status.

Parameters
statusThe status to count (SCHEDULED, STARTED, COMPLETED)
Returns
Result containing number of worklist items with the given status or error

Definition at line 1582 of file index_database.cpp.

1582 {
1583 return worklist_repository_->worklist_count(status);
1584}

Member Data Documentation

◆ audit_repository_

std::shared_ptr<audit_repository> kcenon::pacs::storage::index_database::audit_repository_
mutableprivate

Definition at line 1358 of file index_database.h.

Referenced by audit_count(), and ~index_database().

◆ db_

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

SQLite database handle (used for migrations and fallback)

Definition at line 1342 of file index_database.h.

1342{nullptr};

Referenced by get_storage_stats(), is_open(), native_handle(), schema_version(), verify_integrity(), and ~index_database().

◆ instance_repository_

std::shared_ptr<instance_repository> kcenon::pacs::storage::index_database::instance_repository_
mutableprivate

Definition at line 1354 of file index_database.h.

Referenced by instance_count(), and ~index_database().

◆ migration_runner_

migration_runner kcenon::pacs::storage::index_database::migration_runner_
private

Migration runner for schema management.

Definition at line 1361 of file index_database.h.

Referenced by schema_version().

◆ mpps_repository_

std::shared_ptr<mpps_repository> kcenon::pacs::storage::index_database::mpps_repository_
mutableprivate

Definition at line 1355 of file index_database.h.

Referenced by mpps_count(), and ~index_database().

◆ path_

std::string kcenon::pacs::storage::index_database::path_
private

Database file path.

Definition at line 1345 of file index_database.h.

Referenced by get_storage_stats(), path(), and ~index_database().

◆ patient_repository_

std::shared_ptr<patient_repository> kcenon::pacs::storage::index_database::patient_repository_
mutableprivate

Extracted repositories used by the facade API.

Definition at line 1351 of file index_database.h.

Referenced by patient_count(), and ~index_database().

◆ remove_on_close_

bool kcenon::pacs::storage::index_database::remove_on_close_ {false}
private

Remove the backing file on destruction for adapter-compatible memory DBs.

Definition at line 1348 of file index_database.h.

1348{false};

Referenced by ~index_database().

◆ series_repository_

std::shared_ptr<series_repository> kcenon::pacs::storage::index_database::series_repository_
mutableprivate

Definition at line 1353 of file index_database.h.

Referenced by series_count(), and ~index_database().

◆ study_repository_

std::shared_ptr<study_repository> kcenon::pacs::storage::index_database::study_repository_
mutableprivate

Definition at line 1352 of file index_database.h.

Referenced by study_count(), and ~index_database().

◆ ups_repository_

std::shared_ptr<ups_repository> kcenon::pacs::storage::index_database::ups_repository_
mutableprivate

Definition at line 1357 of file index_database.h.

Referenced by ups_workitem_count(), and ~index_database().

◆ worklist_repository_

std::shared_ptr<worklist_repository> kcenon::pacs::storage::index_database::worklist_repository_
mutableprivate

Definition at line 1356 of file index_database.h.

Referenced by worklist_count(), and ~index_database().


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