19#ifdef PACS_WITH_DATABASE_SYSTEM
28 std::shared_ptr<pacs_database_adapter> db)
29 : base_repository(std::
move(db),
"key_images",
"key_image_id") {}
35auto key_image_repository::find_by_pk(int64_t pk) -> result_type {
36 if (!db() || !db()->is_connected()) {
38 kcenon::common::error_info{-1,
"Database not connected",
"storage"});
41 auto builder = query_builder();
42 builder.select(select_columns())
47 auto result = storage_session().select(builder.build());
48 if (result.is_err()) {
49 return result_type(result.error());
52 if (result.value().empty()) {
53 return result_type(kcenon::common::error_info{
54 -1,
"Key image not found with pk=" + std::to_string(pk),
"storage"});
57 return result_type(map_row_to_entity(result.value()[0]));
60auto key_image_repository::find_by_study(std::string_view study_uid)
62 return find_where(
"study_uid",
"=", std::string(study_uid));
65auto key_image_repository::search(
const key_image_query& query)
67 if (!db() || !db()->is_connected()) {
68 return list_result_type(
69 kcenon::common::error_info{-1,
"Database not connected",
"storage"});
72 auto builder = query_builder();
73 builder.select(select_columns()).from(table_name());
76 std::optional<database::query_condition> condition;
78 if (
query.study_uid.has_value()) {
79 auto cond = database::query_condition(
80 "study_uid",
"=",
query.study_uid.value());
84 if (
query.sop_instance_uid.has_value()) {
85 auto cond = database::query_condition(
86 "sop_instance_uid",
"=",
query.sop_instance_uid.value());
87 if (condition.has_value()) {
88 condition = condition.value() && cond;
94 if (
query.user_id.has_value()) {
96 database::query_condition(
"user_id",
"=",
query.user_id.value());
97 if (condition.has_value()) {
98 condition = condition.value() && cond;
104 if (condition.has_value()) {
105 builder.where(condition.value());
109 builder.order_by(
"created_at", database::sort_order::desc);
112 if (
query.limit > 0) {
113 builder.limit(
query.limit);
114 if (
query.offset > 0) {
115 builder.offset(
query.offset);
119 auto result = storage_session().select(builder.build());
120 if (result.is_err()) {
121 return list_result_type(result.error());
124 std::vector<key_image_record> records;
125 records.reserve(result.value().size());
126 for (
const auto& row : result.value()) {
127 records.push_back(map_row_to_entity(row));
130 return list_result_type(std::move(records));
133auto key_image_repository::count_by_study(std::string_view study_uid)
135 if (!db() || !db()->is_connected()) {
136 return Result<size_t>(
137 kcenon::common::error_info{-1,
"Database not connected",
"storage"});
140 auto builder = query_builder();
141 builder.select({
"pk"})
143 .where(
"study_uid",
"=", std::string(study_uid));
145 auto result = storage_session().select(builder.build());
146 if (result.is_err()) {
147 return Result<size_t>(result.error());
150 return Result<size_t>(result.value().size());
157auto key_image_repository::map_row_to_entity(
const database_row& row)
const
158 -> key_image_record {
161 record.pk = std::stoll(row.at(
"pk"));
162 record.key_image_id = row.at(
"key_image_id");
163 record.study_uid = row.at(
"study_uid");
164 record.sop_instance_uid = row.at(
"sop_instance_uid");
167 auto frame_it = row.find(
"frame_number");
168 if (frame_it != row.end() && !frame_it->second.empty()) {
169 record.frame_number = std::stoi(frame_it->second);
172 record.user_id = row.at(
"user_id");
173 record.reason = row.at(
"reason");
174 record.document_title = row.at(
"document_title");
177 auto created_it = row.find(
"created_at");
178 if (created_it != row.end() && !created_it->second.empty()) {
179 record.created_at = parse_timestamp(created_it->second);
185auto key_image_repository::entity_to_row(
const key_image_record& entity)
const
186 -> std::map<std::string, database_value> {
187 std::map<std::string, database_value> row;
189 row[
"key_image_id"] = entity.key_image_id;
190 row[
"study_uid"] = entity.study_uid;
191 row[
"sop_instance_uid"] = entity.sop_instance_uid;
193 if (entity.frame_number.has_value()) {
194 row[
"frame_number"] =
static_cast<int64_t
>(entity.frame_number.value());
196 row[
"frame_number"] =
nullptr;
199 row[
"user_id"] = entity.user_id;
200 row[
"reason"] = entity.reason;
201 row[
"document_title"] = entity.document_title;
204 if (entity.created_at != std::chrono::system_clock::time_point{}) {
205 row[
"created_at"] = format_timestamp(entity.created_at);
207 row[
"created_at"] = format_timestamp(std::chrono::system_clock::now());
213auto key_image_repository::get_pk(
const key_image_record& entity)
const
215 return entity.key_image_id;
218auto key_image_repository::has_pk(
const key_image_record& entity)
const
220 return !entity.key_image_id.empty();
223auto key_image_repository::select_columns() const -> std::vector<std::
string> {
224 return {
"pk",
"key_image_id",
"study_uid",
"sop_instance_uid",
225 "frame_number",
"user_id",
"reason",
"document_title",
233auto key_image_repository::parse_timestamp(
const std::string& str)
const
234 -> std::chrono::system_clock::time_point {
240 if (std::sscanf(str.c_str(),
"%d-%d-%d %d:%d:%d", &tm.tm_year, &tm.tm_mon,
241 &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
249 auto time = _mkgmtime(&tm);
251 auto time = timegm(&tm);
254 return std::chrono::system_clock::from_time_t(time);
257auto key_image_repository::format_timestamp(
258 std::chrono::system_clock::time_point tp)
const -> std::string {
259 if (tp == std::chrono::system_clock::time_point{}) {
263 auto time = std::chrono::system_clock::to_time_t(tp);
266 gmtime_s(&tm, &time);
268 gmtime_r(&time, &tm);
272 std::strftime(buf,
sizeof(buf),
"%Y-%m-%d %H:%M:%S", &tm);
290[[nodiscard]] std::string to_timestamp_string(
291 std::chrono::system_clock::time_point tp) {
292 if (tp == std::chrono::system_clock::time_point{}) {
295 auto time = std::chrono::system_clock::to_time_t(tp);
298 gmtime_s(&tm, &time);
300 gmtime_r(&time, &tm);
303 std::strftime(buf,
sizeof(buf),
"%Y-%m-%d %H:%M:%S", &tm);
307[[nodiscard]] std::chrono::system_clock::time_point from_timestamp_string(
309 if (!str || str[0] ==
'\0') {
313 if (std::sscanf(str,
"%d-%d-%d %d:%d:%d", &tm.tm_year, &tm.tm_mon,
314 &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
320 auto time = _mkgmtime(&tm);
322 auto time = timegm(&tm);
324 return std::chrono::system_clock::from_time_t(time);
327[[nodiscard]] std::string get_text_column(sqlite3_stmt* stmt,
int col) {
328 auto text =
reinterpret_cast<const char*
>(sqlite3_column_text(stmt, col));
332[[nodiscard]] int64_t get_int64_column(sqlite3_stmt* stmt,
int col,
333 int64_t default_val = 0) {
334 if (sqlite3_column_type(stmt, col) == SQLITE_NULL) {
337 return sqlite3_column_int64(stmt, col);
340[[nodiscard]] std::optional<int> get_optional_int(sqlite3_stmt* stmt,
int col) {
341 if (sqlite3_column_type(stmt, col) == SQLITE_NULL) {
344 return sqlite3_column_int(stmt, col);
347void bind_optional_int(sqlite3_stmt* stmt,
int idx,
348 const std::optional<int>& value) {
349 if (value.has_value()) {
350 sqlite3_bind_int(stmt, idx, value.value());
352 sqlite3_bind_null(stmt, idx);
370 return VoidResult(kcenon::common::error_info{
371 -1,
"Database not initialized",
"key_image_repository"});
374 static constexpr const char* sql = R
"(
375 INSERT INTO key_images (
376 key_image_id, study_uid, sop_instance_uid, frame_number,
377 user_id, reason, document_title, created_at
378 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
379 ON CONFLICT(key_image_id) DO UPDATE SET
380 reason = excluded.reason,
381 document_title = excluded.document_title
384 sqlite3_stmt* stmt = nullptr;
385 if (sqlite3_prepare_v2(db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
386 return VoidResult(kcenon::common::error_info{
388 "Failed to prepare statement: " + std::string(sqlite3_errmsg(db_)),
389 "key_image_repository"});
392 auto now_str = to_timestamp_string(std::chrono::system_clock::now());
395 sqlite3_bind_text(stmt, idx++, record.key_image_id.c_str(), -1,
397 sqlite3_bind_text(stmt, idx++, record.study_uid.c_str(), -1,
399 sqlite3_bind_text(stmt, idx++, record.sop_instance_uid.c_str(), -1,
401 bind_optional_int(stmt, idx++, record.frame_number);
402 sqlite3_bind_text(stmt, idx++, record.user_id.c_str(), -1, SQLITE_TRANSIENT);
403 sqlite3_bind_text(stmt, idx++, record.reason.c_str(), -1, SQLITE_TRANSIENT);
404 sqlite3_bind_text(stmt, idx++, record.document_title.c_str(), -1,
406 sqlite3_bind_text(stmt, idx++, now_str.c_str(), -1, SQLITE_TRANSIENT);
408 auto rc = sqlite3_step(stmt);
409 sqlite3_finalize(stmt);
411 if (rc != SQLITE_DONE) {
412 return VoidResult(kcenon::common::error_info{
414 "Failed to save key image: " + std::string(sqlite3_errmsg(db_)),
415 "key_image_repository"});
418 return kcenon::common::ok();
422 std::string_view key_image_id)
const {
423 if (!
db_)
return std::nullopt;
425 static constexpr const char* sql = R
"(
426 SELECT pk, key_image_id, study_uid, sop_instance_uid, frame_number,
427 user_id, reason, document_title, created_at
428 FROM key_images WHERE key_image_id = ?
431 sqlite3_stmt* stmt = nullptr;
432 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
436 sqlite3_bind_text(stmt, 1, key_image_id.data(),
437 static_cast<int>(key_image_id.size()), SQLITE_TRANSIENT);
439 std::optional<key_image_record> result;
440 if (sqlite3_step(stmt) == SQLITE_ROW) {
444 sqlite3_finalize(stmt);
450 if (!
db_)
return std::nullopt;
452 static constexpr const char* sql = R
"(
453 SELECT pk, key_image_id, study_uid, sop_instance_uid, frame_number,
454 user_id, reason, document_title, created_at
455 FROM key_images WHERE pk = ?
458 sqlite3_stmt* stmt = nullptr;
459 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
463 sqlite3_bind_int64(stmt, 1, pk);
465 std::optional<key_image_record> result;
466 if (sqlite3_step(stmt) == SQLITE_ROW) {
470 sqlite3_finalize(stmt);
475 std::string_view study_uid)
const {
477 query.study_uid = std::string(study_uid);
483 std::vector<key_image_record> result;
484 if (!
db_)
return result;
486 std::ostringstream sql;
488 SELECT pk, key_image_id, study_uid, sop_instance_uid, frame_number,
489 user_id, reason, document_title, created_at
490 FROM key_images WHERE 1=1
493 std::vector<std::pair<int, std::string>> bindings;
496 if (query.study_uid.has_value()) {
497 sql <<
" AND study_uid = ?";
498 bindings.emplace_back(param_idx++, query.study_uid.value());
501 if (query.sop_instance_uid.has_value()) {
502 sql <<
" AND sop_instance_uid = ?";
503 bindings.emplace_back(param_idx++, query.sop_instance_uid.value());
506 if (query.user_id.has_value()) {
507 sql <<
" AND user_id = ?";
508 bindings.emplace_back(param_idx++, query.user_id.value());
511 sql <<
" ORDER BY created_at DESC";
513 if (query.limit > 0) {
514 sql <<
" LIMIT " << query.limit <<
" OFFSET " << query.offset;
517 sqlite3_stmt* stmt =
nullptr;
518 auto sql_str = sql.str();
519 if (sqlite3_prepare_v2(
db_, sql_str.c_str(), -1, &stmt,
nullptr) !=
524 for (
const auto& [idx, value] : bindings) {
525 sqlite3_bind_text(stmt, idx, value.c_str(), -1, SQLITE_TRANSIENT);
528 while (sqlite3_step(stmt) == SQLITE_ROW) {
532 sqlite3_finalize(stmt);
538 return VoidResult(kcenon::common::error_info{
539 -1,
"Database not initialized",
"key_image_repository"});
542 static constexpr const char* sql =
543 "DELETE FROM key_images WHERE key_image_id = ?";
545 sqlite3_stmt* stmt =
nullptr;
546 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
547 return VoidResult(kcenon::common::error_info{
549 "Failed to prepare statement: " + std::string(sqlite3_errmsg(
db_)),
550 "key_image_repository"});
553 sqlite3_bind_text(stmt, 1, key_image_id.data(),
554 static_cast<int>(key_image_id.size()), SQLITE_TRANSIENT);
556 auto rc = sqlite3_step(stmt);
557 sqlite3_finalize(stmt);
559 if (rc != SQLITE_DONE) {
560 return VoidResult(kcenon::common::error_info{
562 "Failed to delete key image: " + std::string(sqlite3_errmsg(
db_)),
563 "key_image_repository"});
566 return kcenon::common::ok();
570 if (!
db_)
return false;
572 static constexpr const char* sql =
573 "SELECT 1 FROM key_images WHERE key_image_id = ?";
575 sqlite3_stmt* stmt =
nullptr;
576 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
580 sqlite3_bind_text(stmt, 1, key_image_id.data(),
581 static_cast<int>(key_image_id.size()), SQLITE_TRANSIENT);
583 bool found = (sqlite3_step(stmt) == SQLITE_ROW);
584 sqlite3_finalize(stmt);
591 static constexpr const char* sql =
"SELECT COUNT(*) FROM key_images";
593 sqlite3_stmt* stmt =
nullptr;
594 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
599 if (sqlite3_step(stmt) == SQLITE_ROW) {
600 result =
static_cast<size_t>(sqlite3_column_int64(stmt, 0));
603 sqlite3_finalize(stmt);
610 static constexpr const char* sql =
611 "SELECT COUNT(*) FROM key_images WHERE study_uid = ?";
613 sqlite3_stmt* stmt =
nullptr;
614 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
618 sqlite3_bind_text(stmt, 1, study_uid.data(),
619 static_cast<int>(study_uid.size()), SQLITE_TRANSIENT);
622 if (sqlite3_step(stmt) == SQLITE_ROW) {
623 result =
static_cast<size_t>(sqlite3_column_int64(stmt, 0));
626 sqlite3_finalize(stmt);
633 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
634 key_image_record record;
637 record.pk = get_int64_column(stmt, col++);
638 record.key_image_id = get_text_column(stmt, col++);
639 record.study_uid = get_text_column(stmt, col++);
640 record.sop_instance_uid = get_text_column(stmt, col++);
641 record.frame_number = get_optional_int(stmt, col++);
642 record.user_id = get_text_column(stmt, col++);
643 record.reason = get_text_column(stmt, col++);
644 record.document_title = get_text_column(stmt, col++);
646 auto created_str = get_text_column(stmt, col++);
647 record.created_at = from_timestamp_string(created_str.c_str());
Repository for key image persistence (legacy SQLite interface)
auto search(const key_image_query &query) const -> std::vector< key_image_record >
key_image_repository(sqlite3 *db)
auto find_by_study(std::string_view study_uid) const -> std::vector< key_image_record >
auto find_by_id(std::string_view key_image_id) const -> std::optional< key_image_record >
auto count_by_study(std::string_view study_uid) const -> size_t
auto remove(std::string_view key_image_id) -> VoidResult
auto count() const -> size_t
auto exists(std::string_view key_image_id) const -> bool
auto find_by_pk(int64_t pk) const -> std::optional< key_image_record >
auto parse_row(void *stmt) const -> key_image_record
auto is_valid() const noexcept -> bool
Repository for key image persistence using base_repository pattern.
@ move
C-MOVE move request/response.
const atna_coded_value query
Query (110112)
@ record
RECORD - Treatment record dose.
Key image record from the database.