14#ifdef PACS_WITH_DATABASE_SYSTEM
22using kcenon::common::ok;
23using kcenon::common::make_error;
29commitment_repository::commitment_repository(
30 std::shared_ptr<pacs_database_adapter> db)
31 : base_repository(std::
move(db),
"storage_commitment",
"transaction_uid") {}
37auto commitment_repository::record_request(
38 const std::string& transaction_uid,
39 const std::string& requesting_ae,
40 const std::vector<services::sop_reference>& references) -> VoidResult {
41 if (!db() || !db()->is_connected()) {
42 return VoidResult(kcenon::common::error_info{
43 -1,
"Database not connected",
"storage"});
46 return in_transaction([&]() -> VoidResult {
49 record.requesting_ae = requesting_ae;
50 record.request_time = format_timestamp(
51 std::chrono::system_clock::now());
52 record.status = commitment_status::pending;
53 record.total_instances =
static_cast<int>(references.size());
55 auto insert_result = insert(record);
56 if (insert_result.is_err()) {
57 return VoidResult(insert_result.error());
60 return insert_references(transaction_uid, references);
64auto commitment_repository::update_result(
65 const std::string& transaction_uid,
66 const services::commitment_result& result) -> VoidResult {
67 if (!db() || !db()->is_connected()) {
68 return VoidResult(kcenon::common::error_info{
69 -1,
"Database not connected",
"storage"});
72 return in_transaction([&]() -> VoidResult {
74 commitment_status new_status;
75 if (result.failed_references.empty() &&
76 !result.success_references.empty()) {
77 new_status = commitment_status::success;
78 }
else if (result.success_references.empty() &&
79 !result.failed_references.empty()) {
80 new_status = commitment_status::failed;
81 }
else if (!result.success_references.empty() &&
82 !result.failed_references.empty()) {
83 new_status = commitment_status::partial;
85 new_status = commitment_status::failed;
89 auto now_str = format_timestamp(std::chrono::system_clock::now());
91 "UPDATE storage_commitment SET status = '" +
92 std::string(to_string(new_status)) +
93 "', completion_time = '" + now_str +
94 "', success_count = " +
95 std::to_string(result.success_references.size()) +
96 ", failure_count = " +
97 std::to_string(result.failed_references.size()) +
100 auto exec_result = storage_session().execute(sql);
101 if (exec_result.is_err()) {
102 return VoidResult(exec_result.error());
106 for (
const auto& ref : result.success_references) {
107 std::string ref_sql =
108 "UPDATE commitment_references SET status = 'success'"
110 "' AND sop_instance_uid = '" + ref.sop_instance_uid +
"'";
111 auto ref_result = storage_session().execute(ref_sql);
112 if (ref_result.is_err()) {
113 return VoidResult(ref_result.error());
118 for (
const auto& [ref, reason] : result.failed_references) {
119 std::string ref_sql =
120 "UPDATE commitment_references SET status = 'failed'"
121 ", failure_reason = " +
122 std::to_string(
static_cast<uint16_t
>(reason)) +
124 "' AND sop_instance_uid = '" + ref.sop_instance_uid +
"'";
125 auto ref_result = storage_session().execute(ref_sql);
126 if (ref_result.is_err()) {
127 return VoidResult(ref_result.error());
135auto commitment_repository::get_status(
const std::string& transaction_uid)
136 -> Result<commitment_status> {
137 auto result = find_by_id(transaction_uid);
138 if (result.is_err()) {
139 return Result<commitment_status>(result.error());
141 return Result<commitment_status>::ok(result.value().status);
144auto commitment_repository::is_duplicate_transaction(
145 const std::string& transaction_uid) ->
bool {
146 auto result = exists(transaction_uid);
147 if (result.is_err())
return false;
148 return result.value();
151auto commitment_repository::get_references(
152 const std::string& transaction_uid)
153 -> Result<std::vector<commitment_reference_record>> {
154 if (!db() || !db()->is_connected()) {
155 return Result<std::vector<commitment_reference_record>>(
156 kcenon::common::error_info{-1,
"Database not connected",
"storage"});
159 auto builder = query_builder();
160 builder.select({
"transaction_uid",
"sop_class_uid",
"sop_instance_uid",
161 "status",
"failure_reason"})
162 .from(
"commitment_references")
163 .where(
"transaction_uid",
"=", transaction_uid);
165 auto result = storage_session().select(builder.build());
166 if (result.is_err()) {
167 return Result<std::vector<commitment_reference_record>>(result.error());
170 std::vector<commitment_reference_record> records;
171 records.reserve(result.value().size());
172 for (
const auto& row : result.value()) {
173 commitment_reference_record ref;
174 ref.transaction_uid = row.at(
"transaction_uid");
175 ref.sop_class_uid = row.at(
"sop_class_uid");
176 ref.sop_instance_uid = row.at(
"sop_instance_uid");
177 ref.status = row.at(
"status");
179 auto reason_it = row.find(
"failure_reason");
180 if (reason_it != row.end() && !reason_it->second.empty()) {
181 ref.failure_reason = std::stoi(reason_it->second);
184 records.push_back(std::move(ref));
187 return Result<std::vector<commitment_reference_record>>::ok(
191auto commitment_repository::find_by_status(commitment_status status,
193 -> list_result_type {
194 return find_where(
"status",
"=",
195 std::string(to_string(status)));
198auto commitment_repository::cleanup_old_transactions(
199 std::chrono::hours max_age) -> Result<size_t> {
200 if (!db() || !db()->is_connected()) {
201 return Result<size_t>(kcenon::common::error_info{
202 -1,
"Database not connected",
"storage"});
205 auto cutoff = std::chrono::system_clock::now() - max_age;
206 auto cutoff_str = format_timestamp(cutoff);
209 std::string ref_sql =
210 "DELETE FROM commitment_references WHERE transaction_uid IN "
211 "(SELECT transaction_uid FROM storage_commitment "
212 "WHERE status IN ('success', 'failed', 'partial') "
213 "AND completion_time < '" + cutoff_str +
"')";
215 auto ref_result = storage_session().execute(ref_sql);
216 if (ref_result.is_err()) {
217 return Result<size_t>(ref_result.error());
221 std::string count_sql =
222 "SELECT count(*) as cnt FROM storage_commitment "
223 "WHERE status IN ('success', 'failed', 'partial') "
224 "AND completion_time < '" + cutoff_str +
"'";
226 auto count_result = storage_session().select(count_sql);
227 size_t deleted_count = 0;
228 if (count_result.is_ok() && !count_result.value().empty()) {
229 deleted_count = std::stoull(count_result.value()[0].at(
"cnt"));
234 "DELETE FROM storage_commitment "
235 "WHERE status IN ('success', 'failed', 'partial') "
236 "AND completion_time < '" + cutoff_str +
"'";
238 auto result = storage_session().execute(sql);
239 if (result.is_err()) {
240 return Result<size_t>(result.error());
243 return kcenon::common::ok(deleted_count);
250auto commitment_repository::map_row_to_entity(
const database_row& row)
const
251 -> commitment_record {
253 record.transaction_uid = row.at(
"transaction_uid");
254 record.requesting_ae = row.at(
"requesting_ae");
255 record.request_time = row.at(
"request_time");
257 auto completion_it = row.find(
"completion_time");
258 if (completion_it != row.end()) {
259 record.completion_time = completion_it->second;
262 record.status = commitment_status_from_string(row.at(
"status"));
264 auto total_it = row.find(
"total_instances");
265 if (total_it != row.end() && !total_it->second.empty()) {
266 record.total_instances = std::stoi(total_it->second);
269 auto success_it = row.find(
"success_count");
270 if (success_it != row.end() && !success_it->second.empty()) {
271 record.success_count = std::stoi(success_it->second);
274 auto failure_it = row.find(
"failure_count");
275 if (failure_it != row.end() && !failure_it->second.empty()) {
276 record.failure_count = std::stoi(failure_it->second);
282auto commitment_repository::entity_to_row(
283 const commitment_record& entity)
const
284 -> std::map<std::string, database_value> {
285 std::map<std::string, database_value> row;
287 row[
"transaction_uid"] = entity.transaction_uid;
288 row[
"requesting_ae"] = entity.requesting_ae;
289 row[
"request_time"] = entity.request_time;
290 row[
"completion_time"] = entity.completion_time;
291 row[
"status"] = std::string(to_string(entity.status));
292 row[
"total_instances"] =
static_cast<int64_t
>(entity.total_instances);
293 row[
"success_count"] =
static_cast<int64_t
>(entity.success_count);
294 row[
"failure_count"] =
static_cast<int64_t
>(entity.failure_count);
299auto commitment_repository::get_pk(
const commitment_record& entity)
const
301 return entity.transaction_uid;
304auto commitment_repository::has_pk(
const commitment_record& entity)
const
306 return !entity.transaction_uid.empty();
313auto commitment_repository::format_timestamp(
314 std::chrono::system_clock::time_point tp)
const -> std::string {
315 auto time_t = std::chrono::system_clock::to_time_t(tp);
318 gmtime_s(&tm, &time_t);
320 gmtime_r(&time_t, &tm);
322 std::ostringstream oss;
323 oss << std::put_time(&tm,
"%Y-%m-%d %H:%M:%S");
327auto commitment_repository::insert_references(
328 const std::string& transaction_uid,
329 const std::vector<services::sop_reference>& references) -> VoidResult {
330 for (
const auto& ref : references) {
332 "INSERT INTO commitment_references "
333 "(transaction_uid, sop_class_uid, sop_instance_uid, status) "
335 ref.sop_class_uid +
"', '" +
336 ref.sop_instance_uid +
"', 'pending')";
338 auto result = storage_session().execute(sql);
339 if (result.is_err()) {
340 return VoidResult(result.error());
Repository for Storage Commitment transaction tracking.
@ move
C-MOVE move request/response.
@ record
RECORD - Treatment record dose.