430 {
432 return VoidResult(kcenon::common::error_info{
433 -1, "Database not initialized", "measurement_repository"});
434 }
435
436 static constexpr const char* sql = R"(
437 INSERT INTO measurements (
438 measurement_id, sop_instance_uid, frame_number, user_id,
439 measurement_type, geometry_json, value, unit, label, created_at
440 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
441 ON CONFLICT(measurement_id) DO UPDATE SET
442 geometry_json = excluded.geometry_json,
443 value = excluded.value,
444 unit = excluded.unit,
445 label = excluded.label
446 )";
447
448 sqlite3_stmt* stmt = nullptr;
449 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
450 return VoidResult(kcenon::common::error_info{
451 -1,
452 "Failed to prepare statement: " + std::string(sqlite3_errmsg(
db_)),
453 "measurement_repository"});
454 }
455
456 auto now_str = to_timestamp_string(std::chrono::system_clock::now());
457
458 int idx = 1;
459 sqlite3_bind_text(stmt, idx++,
record.measurement_id.c_str(), -1,
460 SQLITE_TRANSIENT);
461 sqlite3_bind_text(stmt, idx++,
record.sop_instance_uid.c_str(), -1,
462 SQLITE_TRANSIENT);
463 bind_optional_int(stmt, idx++,
record.frame_number);
464 sqlite3_bind_text(stmt, idx++,
record.user_id.c_str(), -1, SQLITE_TRANSIENT);
466 SQLITE_TRANSIENT);
467 sqlite3_bind_text(stmt, idx++,
record.geometry_json.c_str(), -1,
468 SQLITE_TRANSIENT);
469 sqlite3_bind_double(stmt, idx++,
record.value);
470 sqlite3_bind_text(stmt, idx++,
record.unit.c_str(), -1, SQLITE_TRANSIENT);
471 sqlite3_bind_text(stmt, idx++,
record.label.c_str(), -1, SQLITE_TRANSIENT);
472 sqlite3_bind_text(stmt, idx++, now_str.c_str(), -1, SQLITE_TRANSIENT);
473
474 auto rc = sqlite3_step(stmt);
475 sqlite3_finalize(stmt);
476
477 if (rc != SQLITE_DONE) {
478 return VoidResult(kcenon::common::error_info{
479 -1,
480 "Failed to save measurement: " + std::string(sqlite3_errmsg(
db_)),
481 "measurement_repository"});
482 }
483
484 return kcenon::common::ok();
485}