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

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

#include <prefetch_repository.h>

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

Public Member Functions

 prefetch_repository (sqlite3 *db)
 
 ~prefetch_repository ()
 
 prefetch_repository (const prefetch_repository &)=delete
 
auto operator= (const prefetch_repository &) -> prefetch_repository &=delete
 
 prefetch_repository (prefetch_repository &&) noexcept
 
auto operator= (prefetch_repository &&) noexcept -> prefetch_repository &
 
auto save_rule (const client::prefetch_rule &rule) -> VoidResult
 
auto find_rule_by_id (std::string_view rule_id) const -> std::optional< client::prefetch_rule >
 
auto find_rule_by_pk (int64_t pk) const -> std::optional< client::prefetch_rule >
 
auto find_rules (const prefetch_rule_query_options &options={}) const -> std::vector< client::prefetch_rule >
 
auto find_enabled_rules () const -> std::vector< client::prefetch_rule >
 
auto remove_rule (std::string_view rule_id) -> VoidResult
 
auto rule_exists (std::string_view rule_id) const -> bool
 
auto increment_triggered (std::string_view rule_id) -> VoidResult
 
auto increment_studies_prefetched (std::string_view rule_id, size_t count=1) -> VoidResult
 
auto enable_rule (std::string_view rule_id) -> VoidResult
 
auto disable_rule (std::string_view rule_id) -> VoidResult
 
auto save_history (const client::prefetch_history &history) -> VoidResult
 
auto find_history (const prefetch_history_query_options &options={}) const -> std::vector< client::prefetch_history >
 
auto is_study_prefetched (std::string_view study_uid) const -> bool
 
auto count_completed_today () const -> size_t
 
auto count_failed_today () const -> size_t
 
auto update_history_status (std::string_view study_uid, std::string_view status) -> VoidResult
 
auto cleanup_old_history (std::chrono::hours max_age) -> Result< size_t >
 
auto rule_count () const -> size_t
 
auto enabled_rule_count () const -> size_t
 
auto history_count () const -> size_t
 
auto is_valid () const noexcept -> bool
 
auto initialize_tables () -> VoidResult
 

Private Member Functions

auto parse_rule_row (void *stmt) const -> client::prefetch_rule
 
auto parse_history_row (void *stmt) const -> client::prefetch_history
 

Static Private Member Functions

static auto serialize_modalities (const std::vector< std::string > &modalities) -> std::string
 
static auto deserialize_modalities (std::string_view json) -> std::vector< std::string >
 
static auto serialize_node_ids (const std::vector< std::string > &node_ids) -> std::string
 
static auto deserialize_node_ids (std::string_view json) -> std::vector< std::string >
 

Private Attributes

sqlite3 * db_ {nullptr}
 

Detailed Description

Repository for prefetch persistence (legacy SQLite interface)

Definition at line 414 of file prefetch_repository.h.

Constructor & Destructor Documentation

◆ prefetch_repository() [1/3]

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

Definition at line 1034 of file prefetch_repository.cpp.

◆ ~prefetch_repository()

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

◆ prefetch_repository() [2/3]

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

◆ prefetch_repository() [3/3]

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

Member Function Documentation

◆ cleanup_old_history()

Result< size_t > kcenon::pacs::storage::prefetch_repository::cleanup_old_history ( std::chrono::hours max_age) -> Result<size_t>
nodiscard

Definition at line 1707 of file prefetch_repository.cpp.

1708 {
1709 return VoidResult(kcenon::common::error_info{
1710 -1, "Failed to update status: " + std::string(sqlite3_errmsg(db_)),
1711 "prefetch_repository"});
1712 }
1713
1714 return kcenon::common::ok();
1715}
1716
1717Result<size_t> prefetch_repository::cleanup_old_history(std::chrono::hours max_age) {
1718 if (!db_) {
1719 return Result<size_t>(kcenon::common::error_info{
1720 -1, "Database not initialized", "prefetch_repository"});
1721 }
1722
1723 // Calculate cutoff timestamp
1724 auto cutoff = std::chrono::system_clock::now() - max_age;
1725 auto cutoff_str = to_timestamp_string(cutoff);
1726
1727 static constexpr const char* sql = R"(
1728 DELETE FROM prefetch_history WHERE prefetched_at < ?
1729 )";
1730
1731 sqlite3_stmt* stmt = nullptr;
1732 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1733 return Result<size_t>(kcenon::common::error_info{
1734 -1, "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1735 "prefetch_repository"});
1736 }
1737
1738 sqlite3_bind_text(stmt, 1, cutoff_str.c_str(), -1, SQLITE_TRANSIENT);
1739
1740 auto rc = sqlite3_step(stmt);
auto cleanup_old_history(std::chrono::hours max_age) -> Result< size_t >
kcenon::common::Result< T > Result
Result type alias for operations returning a value.

References db_.

◆ count_completed_today()

size_t kcenon::pacs::storage::prefetch_repository::count_completed_today ( ) const -> size_t
nodiscard

Definition at line 1627 of file prefetch_repository.cpp.

1637 {
1638 if (!db_) return 0;
1639
1640 static constexpr const char* sql = R"(
1641 SELECT COUNT(*) FROM prefetch_history
1642 WHERE status = 'completed'
1643 AND date(prefetched_at) = date('now')
1644 )";
1645
1646 sqlite3_stmt* stmt = nullptr;
1647 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1648 return 0;

◆ count_failed_today()

size_t kcenon::pacs::storage::prefetch_repository::count_failed_today ( ) const -> size_t
nodiscard

Definition at line 1650 of file prefetch_repository.cpp.

1652 {
1653 result = static_cast<size_t>(sqlite3_column_int64(stmt, 0));
1654 }
1655
1656 sqlite3_finalize(stmt);
1657 return result;
1658}
1659
1661 if (!db_) return 0;
1662
1663 static constexpr const char* sql = R"(
1664 SELECT COUNT(*) FROM prefetch_history
1665 WHERE status = 'failed'
1666 AND date(prefetched_at) = date('now')
1667 )";
1668
1669 sqlite3_stmt* stmt = nullptr;
1670 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1671 return 0;

◆ deserialize_modalities()

std::vector< std::string > kcenon::pacs::storage::prefetch_repository::deserialize_modalities ( std::string_view json) -> std::vector<std::string>
staticnodiscardprivate

Definition at line 1002 of file prefetch_repository.cpp.

1003 {
1004 std::vector<std::string> result;
1005 if (json.empty() || json == "[]") return result;
1006
1007 size_t pos = 0;
1008 while (pos < json.size()) {
1009 auto [value, next_pos] = extract_json_string(json, pos);
1010 if (next_pos == std::string_view::npos) break;
1011 if (!value.empty()) {
1012 result.push_back(value);
1013 }
1014 pos = next_pos;
1015 }
1016
1017 return result;
1018}

Referenced by deserialize_node_ids().

Here is the caller graph for this function:

◆ deserialize_node_ids()

std::vector< std::string > kcenon::pacs::storage::prefetch_repository::deserialize_node_ids ( std::string_view json) -> std::vector<std::string>
staticnodiscardprivate

Definition at line 1025 of file prefetch_repository.cpp.

1026 {
1027 return deserialize_modalities(json); // Same format
1028}
static auto deserialize_modalities(std::string_view json) -> std::vector< std::string >

References deserialize_modalities().

Here is the call graph for this function:

◆ disable_rule()

VoidResult kcenon::pacs::storage::prefetch_repository::disable_rule ( std::string_view rule_id) -> VoidResult
nodiscard

Definition at line 1481 of file prefetch_repository.cpp.

1481 : " + std::string(sqlite3_errmsg(db_)),
1483 }
1484
1485 return kcenon::common::ok();
1486}
1487
1488VoidResult prefetch_repository::disable_rule(std::string_view rule_id) {
1489 if (!db_) {
1490 return VoidResult(kcenon::common::error_info{
1491 -1, "Database not initialized", "prefetch_repository"});
1492 }
1493
1494 static constexpr const char* sql = R"(
1495 UPDATE prefetch_rules SET
1496 enabled = 0,
1497 updated_at = CURRENT_TIMESTAMP
1498 WHERE rule_id = ?
1499 )";
1500
1501 sqlite3_stmt* stmt = nullptr;
1502 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1503 return VoidResult(kcenon::common::error_info{
1504 -1, "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1506 }
1507
1508 sqlite3_bind_text(stmt, 1, rule_id.data(), static_cast<int>(rule_id.size()), SQLITE_TRANSIENT);
1509
1510 auto rc = sqlite3_step(stmt);
1511 sqlite3_finalize(stmt);
1512
1513 if (rc != SQLITE_DONE) {

◆ enable_rule()

VoidResult kcenon::pacs::storage::prefetch_repository::enable_rule ( std::string_view rule_id) -> VoidResult
nodiscard

Definition at line 1447 of file prefetch_repository.cpp.

1447 : " + std::string(sqlite3_errmsg(db_)),
1449 }
1450
1451 return kcenon::common::ok();
1452}
1453
1454VoidResult prefetch_repository::enable_rule(std::string_view rule_id) {
1455 if (!db_) {
1456 return VoidResult(kcenon::common::error_info{
1457 -1, "Database not initialized", "prefetch_repository"});
1458 }
1459
1460 static constexpr const char* sql = R"(
1461 UPDATE prefetch_rules SET
1462 enabled = 1,
1463 updated_at = CURRENT_TIMESTAMP
1464 WHERE rule_id = ?
1465 )";
1466
1467 sqlite3_stmt* stmt = nullptr;
1468 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1469 return VoidResult(kcenon::common::error_info{
1470 -1, "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1472 }
1473
1474 sqlite3_bind_text(stmt, 1, rule_id.data(), static_cast<int>(rule_id.size()), SQLITE_TRANSIENT);
1475
1476 auto rc = sqlite3_step(stmt);
1477 sqlite3_finalize(stmt);
1478
1479 if (rc != SQLITE_DONE) {

◆ enabled_rule_count()

size_t kcenon::pacs::storage::prefetch_repository::enabled_rule_count ( ) const -> size_t
nodiscard

Definition at line 1765 of file prefetch_repository.cpp.

1767 {
1768 result = static_cast<size_t>(sqlite3_column_int64(stmt, 0));
1769 }
1770
1771 sqlite3_finalize(stmt);
1772 return result;
1773}
1774
1776 if (!db_) return 0;
1777
1778 static constexpr const char* sql = "SELECT COUNT(*) FROM prefetch_rules WHERE enabled = 1";
1779
1780 sqlite3_stmt* stmt = nullptr;
1781 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1782 return 0;

◆ find_enabled_rules()

std::vector< client::prefetch_rule > kcenon::pacs::storage::prefetch_repository::find_enabled_rules ( ) const -> std::vector<client::prefetch_rule>
nodiscard

Definition at line 1322 of file prefetch_repository.cpp.

◆ find_history()

std::vector< client::prefetch_history > kcenon::pacs::storage::prefetch_repository::find_history ( const prefetch_history_query_options & options = {}) const -> std::vector<client::prefetch_history>
nodiscard

Definition at line 1570 of file prefetch_repository.cpp.

1570 : " + std::string(sqlite3_errmsg(db_)),
1572 }
1573
1574 return kcenon::common::ok();
1575}
1576
1577std::vector<client::prefetch_history> prefetch_repository::find_history(
1578 const prefetch_history_query_options& options) const {
1579 std::vector<client::prefetch_history> result;
1580 if (!db_) return result;
1581
1582 std::ostringstream sql;
1583 sql << R"(
1584 SELECT pk, patient_id, study_uid, rule_id, source_node_id, job_id, status, prefetched_at
1585 FROM prefetch_history WHERE 1=1
1586 )";
1587
1588 if (options.patient_id.has_value()) {
1589 sql << " AND patient_id = '" << options.patient_id.value() << "'";
1590 }
1591
1592 if (options.rule_id.has_value()) {
1593 sql << " AND rule_id = '" << options.rule_id.value() << "'";
1594 }
1595
1596 if (options.status.has_value()) {
1597 sql << " AND status = '" << options.status.value() << "'";
1598 }
1599
1600 sql << " ORDER BY prefetched_at DESC";
1601 sql << " LIMIT " << options.limit << " OFFSET " << options.offset;
1602
1603 sqlite3_stmt* stmt = nullptr;
1604 auto sql_str = sql.str();
1605 if (sqlite3_prepare_v2(db_, sql_str.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
constexpr dicom_tag patient_id
Patient ID.
constexpr dicom_tag status
Status.

◆ find_rule_by_id()

std::optional< client::prefetch_rule > kcenon::pacs::storage::prefetch_repository::find_rule_by_id ( std::string_view rule_id) const -> std::optional<client::prefetch_rule>
nodiscard

Definition at line 1232 of file prefetch_repository.cpp.

1233 {
1234 if (!db_) return std::nullopt;
1235
1236 static constexpr const char* sql = R"(
1237 SELECT pk, rule_id, name, enabled, trigger_type,
1238 modality_filter, body_part_filter, station_ae_filter,
1239 prior_lookback_hours, max_prior_studies, prior_modalities_json,
1240 source_node_ids_json, schedule_cron, advance_time_minutes,
1241 triggered_count, studies_prefetched, last_triggered
1242 FROM prefetch_rules WHERE rule_id = ?
1243 )";
1244
1245 sqlite3_stmt* stmt = nullptr;
1246 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1247 return std::nullopt;
1248 }
1249
1250 sqlite3_bind_text(stmt, 1, rule_id.data(), static_cast<int>(rule_id.size()), SQLITE_TRANSIENT);
1251
1252 std::optional<client::prefetch_rule> result;
1253 if (sqlite3_step(stmt) == SQLITE_ROW) {
1254 result = parse_rule_row(stmt);
1255 }
1256
1257 sqlite3_finalize(stmt);
1258 return result;
1259}
auto parse_rule_row(void *stmt) const -> client::prefetch_rule

References db_, and parse_rule_row().

Here is the call graph for this function:

◆ find_rule_by_pk()

std::optional< client::prefetch_rule > kcenon::pacs::storage::prefetch_repository::find_rule_by_pk ( int64_t pk) const -> std::optional<client::prefetch_rule>
nodiscard

Definition at line 1261 of file prefetch_repository.cpp.

1261 {
1262 if (!db_) return std::nullopt;
1263
1264 static constexpr const char* sql = R"(
1265 SELECT pk, rule_id, name, enabled, trigger_type,
1266 modality_filter, body_part_filter, station_ae_filter,
1267 prior_lookback_hours, max_prior_studies, prior_modalities_json,
1268 source_node_ids_json, schedule_cron, advance_time_minutes,
1269 triggered_count, studies_prefetched, last_triggered
1270 FROM prefetch_rules WHERE pk = ?
1271 )";
1272
1273 sqlite3_stmt* stmt = nullptr;
1274 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1275 return std::nullopt;
1276 }
1277
1278 sqlite3_bind_int64(stmt, 1, pk);
1279
1280 std::optional<client::prefetch_rule> result;
1281 if (sqlite3_step(stmt) == SQLITE_ROW) {
1282 result = parse_rule_row(stmt);
1283 }
1284
1285 sqlite3_finalize(stmt);
1286 return result;
1287}

References db_, and parse_rule_row().

Here is the call graph for this function:

◆ find_rules()

std::vector< client::prefetch_rule > kcenon::pacs::storage::prefetch_repository::find_rules ( const prefetch_rule_query_options & options = {}) const -> std::vector<client::prefetch_rule>
nodiscard

Definition at line 1289 of file prefetch_repository.cpp.

1290 {
1291 std::vector<client::prefetch_rule> result;
1292 if (!db_) return result;
1293
1294 std::ostringstream sql;
1295 sql << R"(
1296 SELECT pk, rule_id, name, enabled, trigger_type,
1297 modality_filter, body_part_filter, station_ae_filter,
1298 prior_lookback_hours, max_prior_studies, prior_modalities_json,
1299 source_node_ids_json, schedule_cron, advance_time_minutes,
1300 triggered_count, studies_prefetched, last_triggered
1301 FROM prefetch_rules WHERE 1=1
1302 )";
1303
1304 if (options.enabled_only.has_value()) {
1305 sql << " AND enabled = " << (options.enabled_only.value() ? "1" : "0");
1306 }
1307
1308 if (options.trigger.has_value()) {
1309 sql << " AND trigger_type = '" << client::to_string(options.trigger.value()) << "'";
1310 }
1311
1312 sql << " ORDER BY created_at DESC";
1313 sql << " LIMIT " << options.limit << " OFFSET " << options.offset;
1314
1315 sqlite3_stmt* stmt = nullptr;
1316 auto sql_str = sql.str();
1317 if (sqlite3_prepare_v2(db_, sql_str.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
1318 return result;
1319 }
1320
constexpr const char * to_string(job_type type) noexcept
Convert job_type to string representation.
Definition job_types.h:54

References db_, and kcenon::pacs::client::to_string().

Referenced by remove_rule().

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

◆ history_count()

size_t kcenon::pacs::storage::prefetch_repository::history_count ( ) const -> size_t
nodiscard

Definition at line 1784 of file prefetch_repository.cpp.

1786 {
1787 result = static_cast<size_t>(sqlite3_column_int64(stmt, 0));
1788 }
1789
1790 sqlite3_finalize(stmt);
1791 return result;
1792}
1793
1795 if (!db_) return 0;
1796
1797 static constexpr const char* sql = "SELECT COUNT(*) FROM prefetch_history";
1798
1799 sqlite3_stmt* stmt = nullptr;
1800 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1801 return 0;

◆ increment_studies_prefetched()

VoidResult kcenon::pacs::storage::prefetch_repository::increment_studies_prefetched ( std::string_view rule_id,
size_t count = 1 ) -> VoidResult
nodiscard

Definition at line 1412 of file prefetch_repository.cpp.

1412 : " + std::string(sqlite3_errmsg(db_)),
1414 }
1415
1416 return kcenon::common::ok();
1417}
1418
1419VoidResult prefetch_repository::increment_studies_prefetched(
1420 std::string_view rule_id, size_t count) {
1421 if (!db_) {
1422 return VoidResult(kcenon::common::error_info{
1423 -1, "Database not initialized", "prefetch_repository"});
1424 }
1425
1426 static constexpr const char* sql = R"(
1427 UPDATE prefetch_rules SET
1428 studies_prefetched = studies_prefetched + ?
1429 WHERE rule_id = ?
1430 )";
1431
1432 sqlite3_stmt* stmt = nullptr;
1433 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1434 return VoidResult(kcenon::common::error_info{
1435 -1, "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1437 }
1438
1439 sqlite3_bind_int64(stmt, 1, static_cast<int64_t>(count));
1440 sqlite3_bind_text(stmt, 2, rule_id.data(), static_cast<int>(rule_id.size()), SQLITE_TRANSIENT);
1441
1442 auto rc = sqlite3_step(stmt);
1443 sqlite3_finalize(stmt);
1444
1445 if (rc != SQLITE_DONE) {

◆ increment_triggered()

VoidResult kcenon::pacs::storage::prefetch_repository::increment_triggered ( std::string_view rule_id) -> VoidResult
nodiscard

Definition at line 1378 of file prefetch_repository.cpp.

1385 {
1386 if (!db_) {
1387 return VoidResult(kcenon::common::error_info{
1388 -1, "Database not initialized", "prefetch_repository"});
1389 }
1390
1391 static constexpr const char* sql = R"(
1392 UPDATE prefetch_rules SET
1393 triggered_count = triggered_count + 1,
1394 last_triggered = CURRENT_TIMESTAMP
1395 WHERE rule_id = ?
1396 )";
1397
1398 sqlite3_stmt* stmt = nullptr;
1399 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1400 return VoidResult(kcenon::common::error_info{
1401 -1, "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1402 "prefetch_repository"});
1403 }
1404
1405 sqlite3_bind_text(stmt, 1, rule_id.data(), static_cast<int>(rule_id.size()), SQLITE_TRANSIENT);
1406
1407 auto rc = sqlite3_step(stmt);
1408 sqlite3_finalize(stmt);
1409
1410 if (rc != SQLITE_DONE) {

◆ initialize_tables()

VoidResult kcenon::pacs::storage::prefetch_repository::initialize_tables ( ) -> VoidResult
nodiscard

Definition at line 1046 of file prefetch_repository.cpp.

1046 {
1047 if (!db_) {
1048 return VoidResult(kcenon::common::error_info{
1049 -1, "Database not initialized", "prefetch_repository"});
1050 }
1051
1052 // Create prefetch_rules table
1053 static constexpr const char* create_rules_sql = R"(
1054 CREATE TABLE IF NOT EXISTS prefetch_rules (
1055 pk INTEGER PRIMARY KEY AUTOINCREMENT,
1056 rule_id TEXT UNIQUE NOT NULL,
1057 name TEXT NOT NULL,
1058 enabled INTEGER DEFAULT 1,
1059 trigger_type TEXT NOT NULL,
1060 modality_filter TEXT,
1061 body_part_filter TEXT,
1062 station_ae_filter TEXT,
1063 prior_lookback_hours INTEGER DEFAULT 8760,
1064 max_prior_studies INTEGER DEFAULT 3,
1065 prior_modalities_json TEXT,
1066 source_node_ids_json TEXT NOT NULL,
1067 schedule_cron TEXT,
1068 advance_time_minutes INTEGER DEFAULT 60,
1069 triggered_count INTEGER DEFAULT 0,
1070 studies_prefetched INTEGER DEFAULT 0,
1071 last_triggered TIMESTAMP,
1072 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1073 updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1074 )
1075 )";
1076
1077 char* err_msg = nullptr;
1078 int rc = sqlite3_exec(db_, create_rules_sql, nullptr, nullptr, &err_msg);
1079 if (rc != SQLITE_OK) {
1080 std::string error = err_msg ? err_msg : "Unknown error";
1081 sqlite3_free(err_msg);
1082 return VoidResult(kcenon::common::error_info{
1083 -1, "Failed to create prefetch_rules table: " + error,
1084 "prefetch_repository"});
1085 }
1086
1087 // Create prefetch_history table
1088 static constexpr const char* create_history_sql = R"(
1089 CREATE TABLE IF NOT EXISTS prefetch_history (
1090 pk INTEGER PRIMARY KEY AUTOINCREMENT,
1091 patient_id TEXT NOT NULL,
1092 study_uid TEXT NOT NULL,
1093 rule_id TEXT,
1094 source_node_id TEXT NOT NULL,
1095 job_id TEXT,
1096 status TEXT NOT NULL,
1097 prefetched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1098 )
1099 )";
1100
1101 rc = sqlite3_exec(db_, create_history_sql, nullptr, nullptr, &err_msg);
1102 if (rc != SQLITE_OK) {
1103 std::string error = err_msg ? err_msg : "Unknown error";
1104 sqlite3_free(err_msg);
1105 return VoidResult(kcenon::common::error_info{
1106 -1, "Failed to create prefetch_history table: " + error,
1107 "prefetch_repository"});
1108 }
1109
1110 // Create indexes
1111 static constexpr const char* create_indexes_sql = R"(
1112 CREATE INDEX IF NOT EXISTS idx_prefetch_history_patient ON prefetch_history(patient_id);
1113 CREATE INDEX IF NOT EXISTS idx_prefetch_history_study ON prefetch_history(study_uid);
1114 CREATE INDEX IF NOT EXISTS idx_prefetch_history_status ON prefetch_history(status);
1115 )";
1116
1117 rc = sqlite3_exec(db_, create_indexes_sql, nullptr, nullptr, &err_msg);
1118 if (rc != SQLITE_OK) {
1119 std::string error = err_msg ? err_msg : "Unknown error";
1120 sqlite3_free(err_msg);
1121 return VoidResult(kcenon::common::error_info{
1122 -1, "Failed to create indexes: " + error,
1123 "prefetch_repository"});
1124 }
1125
1126 return kcenon::common::ok();
1127}

References kcenon::pacs::storage::error.

◆ is_study_prefetched()

bool kcenon::pacs::storage::prefetch_repository::is_study_prefetched ( std::string_view study_uid) const -> bool
nodiscard

Definition at line 1607 of file prefetch_repository.cpp.

1609 {
1610 result.push_back(parse_history_row(stmt));
1611 }
1612
1613 sqlite3_finalize(stmt);
1614 return result;
1615}
1616
1617bool prefetch_repository::is_study_prefetched(std::string_view study_uid) const {
1618 if (!db_) return false;
1619
1620 static constexpr const char* sql = R"(
1621 SELECT 1 FROM prefetch_history
1622 WHERE study_uid = ? AND status IN ('completed', 'pending')
1623 )";
1624
1625 sqlite3_stmt* stmt = nullptr;
auto is_study_prefetched(std::string_view study_uid) const -> bool
auto parse_history_row(void *stmt) const -> client::prefetch_history

◆ is_valid()

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

Definition at line 1807 of file prefetch_repository.cpp.

◆ operator=() [1/2]

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

◆ operator=() [2/2]

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

◆ parse_history_row()

client::prefetch_history kcenon::pacs::storage::prefetch_repository::parse_history_row ( void * stmt) const -> client::prefetch_history
nodiscardprivate

Definition at line 1851 of file prefetch_repository.cpp.

1861 {
1862 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
1863 client::prefetch_history history;
1864
1865 int col = 0;
1866 history.pk = get_int64_column(stmt, col++);
1867 history.patient_id = get_text_column(stmt, col++);
1868 history.study_uid = get_text_column(stmt, col++);

◆ parse_rule_row()

client::prefetch_rule kcenon::pacs::storage::prefetch_repository::parse_rule_row ( void * stmt) const -> client::prefetch_rule
nodiscardprivate

Definition at line 1815 of file prefetch_repository.cpp.

1817 {
1818 return db_ != nullptr;
1819}
1820
1821// =============================================================================
1822// Private Implementation
1823// =============================================================================
1824
1825client::prefetch_rule prefetch_repository::parse_rule_row(void* stmt_ptr) const {
1826 auto* stmt = static_cast<sqlite3_stmt*>(stmt_ptr);
1827 client::prefetch_rule rule;
1828
1829 int col = 0;
1830 rule.pk = get_int64_column(stmt, col++);
1831 rule.rule_id = get_text_column(stmt, col++);
1832 rule.name = get_text_column(stmt, col++);
1833 rule.enabled = (get_int_column(stmt, col++) != 0);
1834 rule.trigger = client::prefetch_trigger_from_string(get_text_column(stmt, col++));
1835
1836 rule.modality_filter = get_text_column(stmt, col++);
1837 rule.body_part_filter = get_text_column(stmt, col++);
1838 rule.station_ae_filter = get_text_column(stmt, col++);
1839
1840 rule.prior_lookback = std::chrono::hours{get_int64_column(stmt, col++)};
1841 rule.max_prior_studies = static_cast<size_t>(get_int64_column(stmt, col++));
1842
1843 auto modalities_json = get_text_column(stmt, col++);
1844 rule.prior_modalities = deserialize_modalities(modalities_json);
1845
1846 auto node_ids_json = get_text_column(stmt, col++);
1847 rule.source_node_ids = deserialize_node_ids(node_ids_json);
1848
1849 rule.schedule_cron = get_text_column(stmt, col++);
static auto deserialize_node_ids(std::string_view json) -> std::vector< std::string >
prefetch_trigger prefetch_trigger_from_string(std::string_view str) noexcept
Parse prefetch_trigger from string.

References db_.

Referenced by find_rule_by_id(), and find_rule_by_pk().

Here is the caller graph for this function:

◆ remove_rule()

VoidResult kcenon::pacs::storage::prefetch_repository::remove_rule ( std::string_view rule_id) -> VoidResult
nodiscard

Definition at line 1328 of file prefetch_repository.cpp.

1329 {
1330 prefetch_rule_query_options options;
1331 options.enabled_only = true;
1332 return find_rules(options);
1333}
1334
1335VoidResult prefetch_repository::remove_rule(std::string_view rule_id) {
1336 if (!db_) {
1337 return VoidResult(kcenon::common::error_info{
1338 -1, "Database not initialized", "prefetch_repository"});
1339 }
1340
1341 static constexpr const char* sql = "DELETE FROM prefetch_rules WHERE rule_id = ?";
1342
1343 sqlite3_stmt* stmt = nullptr;
1344 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1345 return VoidResult(kcenon::common::error_info{
1346 -1, "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1347 "prefetch_repository"});
1348 }
1349
1350 sqlite3_bind_text(stmt, 1, rule_id.data(), static_cast<int>(rule_id.size()), SQLITE_TRANSIENT);
1351
1352 auto rc = sqlite3_step(stmt);
1353 sqlite3_finalize(stmt);
1354
1355 if (rc != SQLITE_DONE) {
auto find_rules(const prefetch_rule_query_options &options={}) const -> std::vector< client::prefetch_rule >
auto remove_rule(std::string_view rule_id) -> VoidResult

References kcenon::pacs::storage::prefetch_rule_query_options::enabled_only, and find_rules().

Here is the call graph for this function:

◆ rule_count()

size_t kcenon::pacs::storage::prefetch_repository::rule_count ( ) const -> size_t
nodiscard

Definition at line 1746 of file prefetch_repository.cpp.

1756 {
1757 if (!db_) return 0;
1758
1759 static constexpr const char* sql = "SELECT COUNT(*) FROM prefetch_rules";
1760
1761 sqlite3_stmt* stmt = nullptr;
1762 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1763 return 0;

◆ rule_exists()

bool kcenon::pacs::storage::prefetch_repository::rule_exists ( std::string_view rule_id) const -> bool
nodiscard

Definition at line 1357 of file prefetch_repository.cpp.

1357 : " + std::string(sqlite3_errmsg(db_)),
1359 }
1360
1361 return kcenon::common::ok();
1362}
1363
1364bool prefetch_repository::rule_exists(std::string_view rule_id) const {
1365 if (!db_) return false;
1366
1367 static constexpr const char* sql = "SELECT 1 FROM prefetch_rules WHERE rule_id = ?";
1368
1369 sqlite3_stmt* stmt = nullptr;
1370 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1371 return false;
1372 }

◆ save_history()

VoidResult kcenon::pacs::storage::prefetch_repository::save_history ( const client::prefetch_history & history) -> VoidResult
nodiscard

Definition at line 1519 of file prefetch_repository.cpp.

1526 {
1527 if (!db_) {
1528 return VoidResult(kcenon::common::error_info{
1529 -1, "Database not initialized", "prefetch_repository"});
1530 }
1531
1532 static constexpr const char* sql = R"(
1533 INSERT INTO prefetch_history (
1534 patient_id, study_uid, rule_id, source_node_id, job_id, status
1535 ) VALUES (?, ?, ?, ?, ?, ?)
1536 )";
1537
1538 sqlite3_stmt* stmt = nullptr;
1539 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1540 return VoidResult(kcenon::common::error_info{
1541 -1, "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1542 "prefetch_repository"});
1543 }
1544
1545 int idx = 1;
1546 sqlite3_bind_text(stmt, idx++, history.patient_id.c_str(), -1, SQLITE_TRANSIENT);
1547 sqlite3_bind_text(stmt, idx++, history.study_uid.c_str(), -1, SQLITE_TRANSIENT);
1548
1549 if (history.rule_id.empty()) {
1550 sqlite3_bind_null(stmt, idx++);
1551 } else {
1552 sqlite3_bind_text(stmt, idx++, history.rule_id.c_str(), -1, SQLITE_TRANSIENT);
1553 }
1554
1555 sqlite3_bind_text(stmt, idx++, history.source_node_id.c_str(), -1, SQLITE_TRANSIENT);
1556
1557 if (history.job_id.empty()) {
1558 sqlite3_bind_null(stmt, idx++);
1559 } else {
1560 sqlite3_bind_text(stmt, idx++, history.job_id.c_str(), -1, SQLITE_TRANSIENT);
1561 }
1562
1563 sqlite3_bind_text(stmt, idx++, history.status.c_str(), -1, SQLITE_TRANSIENT);
1564
1565 auto rc = sqlite3_step(stmt);
1566 sqlite3_finalize(stmt);
1567
1568 if (rc != SQLITE_DONE) {

◆ save_rule()

VoidResult kcenon::pacs::storage::prefetch_repository::save_rule ( const client::prefetch_rule & rule) -> VoidResult
nodiscard

Definition at line 1133 of file prefetch_repository.cpp.

1133 {
1134 if (!db_) {
1135 return VoidResult(kcenon::common::error_info{
1136 -1, "Database not initialized", "prefetch_repository"});
1137 }
1138
1139 static constexpr const char* sql = R"(
1140 INSERT INTO prefetch_rules (
1141 rule_id, name, enabled, trigger_type,
1142 modality_filter, body_part_filter, station_ae_filter,
1143 prior_lookback_hours, max_prior_studies, prior_modalities_json,
1144 source_node_ids_json, schedule_cron, advance_time_minutes,
1145 triggered_count, studies_prefetched, last_triggered
1146 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1147 ON CONFLICT(rule_id) DO UPDATE SET
1148 name = excluded.name,
1149 enabled = excluded.enabled,
1150 trigger_type = excluded.trigger_type,
1151 modality_filter = excluded.modality_filter,
1152 body_part_filter = excluded.body_part_filter,
1153 station_ae_filter = excluded.station_ae_filter,
1154 prior_lookback_hours = excluded.prior_lookback_hours,
1155 max_prior_studies = excluded.max_prior_studies,
1156 prior_modalities_json = excluded.prior_modalities_json,
1157 source_node_ids_json = excluded.source_node_ids_json,
1158 schedule_cron = excluded.schedule_cron,
1159 advance_time_minutes = excluded.advance_time_minutes,
1160 updated_at = CURRENT_TIMESTAMP
1161 )";
1162
1163 sqlite3_stmt* stmt = nullptr;
1164 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1165 return VoidResult(kcenon::common::error_info{
1166 -1, "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1167 "prefetch_repository"});
1168 }
1169
1170 int idx = 1;
1171 sqlite3_bind_text(stmt, idx++, rule.rule_id.c_str(), -1, SQLITE_TRANSIENT);
1172 sqlite3_bind_text(stmt, idx++, rule.name.c_str(), -1, SQLITE_TRANSIENT);
1173 sqlite3_bind_int(stmt, idx++, rule.enabled ? 1 : 0);
1174 sqlite3_bind_text(stmt, idx++, client::to_string(rule.trigger), -1, SQLITE_STATIC);
1175
1176 if (rule.modality_filter.empty()) {
1177 sqlite3_bind_null(stmt, idx++);
1178 } else {
1179 sqlite3_bind_text(stmt, idx++, rule.modality_filter.c_str(), -1, SQLITE_TRANSIENT);
1180 }
1181
1182 if (rule.body_part_filter.empty()) {
1183 sqlite3_bind_null(stmt, idx++);
1184 } else {
1185 sqlite3_bind_text(stmt, idx++, rule.body_part_filter.c_str(), -1, SQLITE_TRANSIENT);
1186 }
1187
1188 if (rule.station_ae_filter.empty()) {
1189 sqlite3_bind_null(stmt, idx++);
1190 } else {
1191 sqlite3_bind_text(stmt, idx++, rule.station_ae_filter.c_str(), -1, SQLITE_TRANSIENT);
1192 }
1193
1194 sqlite3_bind_int64(stmt, idx++, rule.prior_lookback.count());
1195 sqlite3_bind_int64(stmt, idx++, static_cast<int64_t>(rule.max_prior_studies));
1196
1197 auto modalities_json = serialize_modalities(rule.prior_modalities);
1198 sqlite3_bind_text(stmt, idx++, modalities_json.c_str(), -1, SQLITE_TRANSIENT);
1199
1200 auto node_ids_json = serialize_node_ids(rule.source_node_ids);
1201 sqlite3_bind_text(stmt, idx++, node_ids_json.c_str(), -1, SQLITE_TRANSIENT);
1202
1203 if (rule.schedule_cron.empty()) {
1204 sqlite3_bind_null(stmt, idx++);
1205 } else {
1206 sqlite3_bind_text(stmt, idx++, rule.schedule_cron.c_str(), -1, SQLITE_TRANSIENT);
1207 }
1208
1209 sqlite3_bind_int64(stmt, idx++, rule.advance_time.count());
1210 sqlite3_bind_int64(stmt, idx++, static_cast<int64_t>(rule.triggered_count));
1211 sqlite3_bind_int64(stmt, idx++, static_cast<int64_t>(rule.studies_prefetched));
1212
1213 auto last_triggered_str = to_timestamp_string(rule.last_triggered);
1214 if (last_triggered_str.empty()) {
1215 sqlite3_bind_null(stmt, idx++);
1216 } else {
1217 sqlite3_bind_text(stmt, idx++, last_triggered_str.c_str(), -1, SQLITE_TRANSIENT);
1218 }
1219
1220 auto rc = sqlite3_step(stmt);
1221 sqlite3_finalize(stmt);
1222
1223 if (rc != SQLITE_DONE) {
1224 return VoidResult(kcenon::common::error_info{
1225 -1, "Failed to save rule: " + std::string(sqlite3_errmsg(db_)),
1226 "prefetch_repository"});
1227 }
1228
1229 return kcenon::common::ok();
1230}
static auto serialize_node_ids(const std::vector< std::string > &node_ids) -> std::string
static auto serialize_modalities(const std::vector< std::string > &modalities) -> std::string

References kcenon::pacs::client::prefetch_rule::advance_time, kcenon::pacs::client::prefetch_rule::body_part_filter, db_, kcenon::pacs::client::prefetch_rule::enabled, kcenon::pacs::client::prefetch_rule::last_triggered, kcenon::pacs::client::prefetch_rule::max_prior_studies, kcenon::pacs::client::prefetch_rule::modality_filter, kcenon::pacs::client::prefetch_rule::name, kcenon::pacs::client::prefetch_rule::prior_lookback, kcenon::pacs::client::prefetch_rule::prior_modalities, kcenon::pacs::client::prefetch_rule::rule_id, kcenon::pacs::client::prefetch_rule::schedule_cron, serialize_modalities(), serialize_node_ids(), kcenon::pacs::client::prefetch_rule::source_node_ids, kcenon::pacs::client::prefetch_rule::station_ae_filter, kcenon::pacs::client::prefetch_rule::studies_prefetched, kcenon::pacs::client::to_string(), kcenon::pacs::client::prefetch_rule::trigger, and kcenon::pacs::client::prefetch_rule::triggered_count.

Here is the call graph for this function:

◆ serialize_modalities()

std::string kcenon::pacs::storage::prefetch_repository::serialize_modalities ( const std::vector< std::string > & modalities) -> std::string
staticnodiscardprivate

Definition at line 988 of file prefetch_repository.cpp.

989 {
990 if (modalities.empty()) return "[]";
991
992 std::ostringstream oss;
993 oss << "[";
994 for (size_t i = 0; i < modalities.size(); ++i) {
995 if (i > 0) oss << ",";
996 oss << "\"" << escape_json_string(modalities[i]) << "\"";
997 }
998 oss << "]";
999 return oss.str();
1000}

Referenced by save_rule(), and serialize_node_ids().

Here is the caller graph for this function:

◆ serialize_node_ids()

std::string kcenon::pacs::storage::prefetch_repository::serialize_node_ids ( const std::vector< std::string > & node_ids) -> std::string
staticnodiscardprivate

Definition at line 1020 of file prefetch_repository.cpp.

1021 {
1022 return serialize_modalities(node_ids); // Same format
1023}

References serialize_modalities().

Referenced by save_rule().

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

◆ update_history_status()

VoidResult kcenon::pacs::storage::prefetch_repository::update_history_status ( std::string_view study_uid,
std::string_view status ) -> VoidResult
nodiscard

Definition at line 1673 of file prefetch_repository.cpp.

1675 {
1676 result = static_cast<size_t>(sqlite3_column_int64(stmt, 0));
1677 }
1678
1679 sqlite3_finalize(stmt);
1680 return result;
1681}
1682
1684 std::string_view study_uid,
1685 std::string_view status) {
1686 if (!db_) {
1687 return VoidResult(kcenon::common::error_info{
1688 -1, "Database not initialized", "prefetch_repository"});
1689 }
1690
1691 static constexpr const char* sql = R"(
1692 UPDATE prefetch_history SET status = ? WHERE study_uid = ?
1693 )";
1694
1695 sqlite3_stmt* stmt = nullptr;
1696 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
1697 return VoidResult(kcenon::common::error_info{
1698 -1, "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
1699 "prefetch_repository"});
1700 }
1701
1702 sqlite3_bind_text(stmt, 1, status.data(), static_cast<int>(status.size()), SQLITE_TRANSIENT);
1703 sqlite3_bind_text(stmt, 2, study_uid.data(), static_cast<int>(study_uid.size()), SQLITE_TRANSIENT);
1704
1705 auto rc = sqlite3_step(stmt);
auto update_history_status(std::string_view study_uid, std::string_view status) -> VoidResult

Member Data Documentation

◆ db_

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

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