461 {
463 return kcenon::common::make_error<int64_t>(
464 -1, "Database not initialized", "node_repository");
465 }
466
467 static constexpr const char* sql = R"(
468 INSERT INTO remote_nodes (
469 node_id, name, ae_title, host, port,
470 supports_find, supports_move, supports_get, supports_store, supports_worklist,
471 connection_timeout_sec, dimse_timeout_sec, max_associations,
472 status, last_verified, last_error, last_error_message,
473 created_at, updated_at
474 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
475 ON CONFLICT(node_id) DO UPDATE SET
476 name = excluded.name,
477 ae_title = excluded.ae_title,
478 host = excluded.host,
479 port = excluded.port,
480 supports_find = excluded.supports_find,
481 supports_move = excluded.supports_move,
482 supports_get = excluded.supports_get,
483 supports_store = excluded.supports_store,
484 supports_worklist = excluded.supports_worklist,
485 connection_timeout_sec = excluded.connection_timeout_sec,
486 dimse_timeout_sec = excluded.dimse_timeout_sec,
487 max_associations = excluded.max_associations,
488 updated_at = CURRENT_TIMESTAMP
489 RETURNING pk
490 )";
491
492 sqlite3_stmt* stmt = nullptr;
493 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
494 return kcenon::common::make_error<int64_t>(
495 -1,
"Failed to prepare statement: " + std::string(sqlite3_errmsg(
db_)),
496 "node_repository");
497 }
498
499 int idx = 1;
500 sqlite3_bind_text(stmt, idx++, node.node_id.c_str(), -1, SQLITE_TRANSIENT);
501 sqlite3_bind_text(stmt, idx++, node.name.c_str(), -1, SQLITE_TRANSIENT);
502 sqlite3_bind_text(stmt, idx++, node.ae_title.c_str(), -1, SQLITE_TRANSIENT);
503 sqlite3_bind_text(stmt, idx++, node.host.c_str(), -1, SQLITE_TRANSIENT);
504 sqlite3_bind_int(stmt, idx++, node.port);
505 sqlite3_bind_int(stmt, idx++, node.supports_find ? 1 : 0);
506 sqlite3_bind_int(stmt, idx++, node.supports_move ? 1 : 0);
507 sqlite3_bind_int(stmt, idx++, node.supports_get ? 1 : 0);
508 sqlite3_bind_int(stmt, idx++, node.supports_store ? 1 : 0);
509 sqlite3_bind_int(stmt, idx++, node.supports_worklist ? 1 : 0);
510 sqlite3_bind_int(stmt, idx++, static_cast<int>(node.connection_timeout.count()));
511 sqlite3_bind_int(stmt, idx++, static_cast<int>(node.dimse_timeout.count()));
512 sqlite3_bind_int(stmt, idx++, static_cast<int>(node.max_associations));
513
515 sqlite3_bind_text(stmt, idx++, status_str.c_str(), -1, SQLITE_TRANSIENT);
516
517 auto last_verified_str = to_timestamp_string(node.last_verified);
518 if (last_verified_str.empty()) {
519 sqlite3_bind_null(stmt, idx++);
520 } else {
521 sqlite3_bind_text(stmt, idx++, last_verified_str.c_str(), -1, SQLITE_TRANSIENT);
522 }
523
524 auto last_error_str = to_timestamp_string(node.last_error);
525 if (last_error_str.empty()) {
526 sqlite3_bind_null(stmt, idx++);
527 } else {
528 sqlite3_bind_text(stmt, idx++, last_error_str.c_str(), -1, SQLITE_TRANSIENT);
529 }
530
531 sqlite3_bind_text(stmt, idx++, node.last_error_message.c_str(), -1, SQLITE_TRANSIENT);
532
533 int64_t pk = 0;
534 if (sqlite3_step(stmt) == SQLITE_ROW) {
535 pk = sqlite3_column_int64(stmt, 0);
536 } else {
537 auto err = std::string(sqlite3_errmsg(
db_));
538 sqlite3_finalize(stmt);
539 return kcenon::common::make_error<int64_t>(-1, "Failed to upsert: " + err, "node_repository");
540 }
541
542 sqlite3_finalize(stmt);
543 return kcenon::common::ok(pk);
544}