Insert or update an instance record from an existing record struct.
792 {
793 if (
record.sop_uid.empty()) {
794 return make_error<int64_t>(-1, "SOP Instance UID is required",
795 "storage");
796 }
797
798 if (
record.sop_uid.length() > 64) {
799 return make_error<int64_t>(
800 -1, "SOP Instance UID exceeds maximum length of 64 characters",
801 "storage");
802 }
803
804 if (
record.series_pk <= 0) {
805 return make_error<int64_t>(-1, "Valid series_pk is required",
806 "storage");
807 }
808
809 if (
record.file_path.empty()) {
810 return make_error<int64_t>(-1, "File path is required", "storage");
811 }
812
813 if (
record.file_size < 0) {
814 return make_error<int64_t>(-1, "File size must be non-negative",
815 "storage");
816 }
817
818 const char* sql = R"(
819 INSERT INTO instances (
820 series_pk, sop_uid, sop_class_uid, instance_number,
821 transfer_syntax, content_date, content_time,
822 rows, columns, bits_allocated, number_of_frames,
823 file_path, file_size, file_hash
824 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
825 ON CONFLICT(sop_uid) DO UPDATE SET
826 series_pk = excluded.series_pk,
827 sop_class_uid = excluded.sop_class_uid,
828 instance_number = excluded.instance_number,
829 transfer_syntax = excluded.transfer_syntax,
830 content_date = excluded.content_date,
831 content_time = excluded.content_time,
832 rows = excluded.rows,
833 columns = excluded.columns,
834 bits_allocated = excluded.bits_allocated,
835 number_of_frames = excluded.number_of_frames,
836 file_path = excluded.file_path,
837 file_size = excluded.file_size,
838 file_hash = excluded.file_hash
839 RETURNING instance_pk;
840 )";
841
842 sqlite3_stmt* stmt = nullptr;
843 auto rc = sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr);
844 if (rc != SQLITE_OK) {
845 return make_error<int64_t>(
846 rc,
847 kcenon::pacs::compat::format("Failed to prepare statement: {}",
848 sqlite3_errmsg(
db_)),
849 "storage");
850 }
851
852 sqlite3_bind_int64(stmt, 1,
record.series_pk);
853 sqlite3_bind_text(stmt, 2,
record.sop_uid.c_str(), -1, SQLITE_TRANSIENT);
854 sqlite3_bind_text(stmt, 3,
record.sop_class_uid.c_str(), -1,
855 SQLITE_TRANSIENT);
856
857 if (
record.instance_number.has_value()) {
858 sqlite3_bind_int(stmt, 4, *
record.instance_number);
859 } else {
860 sqlite3_bind_null(stmt, 4);
861 }
862
863 sqlite3_bind_text(stmt, 5,
record.transfer_syntax.c_str(), -1,
864 SQLITE_TRANSIENT);
865 sqlite3_bind_text(stmt, 6,
record.content_date.c_str(), -1,
866 SQLITE_TRANSIENT);
867 sqlite3_bind_text(stmt, 7,
record.content_time.c_str(), -1,
868 SQLITE_TRANSIENT);
869
870 if (
record.rows.has_value()) {
871 sqlite3_bind_int(stmt, 8, *
record.rows);
872 } else {
873 sqlite3_bind_null(stmt, 8);
874 }
875 if (
record.columns.has_value()) {
876 sqlite3_bind_int(stmt, 9, *
record.columns);
877 } else {
878 sqlite3_bind_null(stmt, 9);
879 }
880 if (
record.bits_allocated.has_value()) {
881 sqlite3_bind_int(stmt, 10, *
record.bits_allocated);
882 } else {
883 sqlite3_bind_null(stmt, 10);
884 }
885 if (
record.number_of_frames.has_value()) {
886 sqlite3_bind_int(stmt, 11, *
record.number_of_frames);
887 } else {
888 sqlite3_bind_null(stmt, 11);
889 }
890
891 sqlite3_bind_text(stmt, 12,
record.file_path.c_str(), -1, SQLITE_TRANSIENT);
892 sqlite3_bind_int64(stmt, 13,
record.file_size);
893 sqlite3_bind_text(stmt, 14,
record.file_hash.c_str(), -1, SQLITE_TRANSIENT);
894
895 rc = sqlite3_step(stmt);
896 if (rc != SQLITE_ROW) {
897 auto error_msg = sqlite3_errmsg(
db_);
898 sqlite3_finalize(stmt);
899 return make_error<int64_t>(
900 rc,
901 kcenon::pacs::compat::format("Failed to upsert instance: {}", error_msg),
902 "storage");
903 }
904
905 auto pk = sqlite3_column_int64(stmt, 0);
906 sqlite3_finalize(stmt);
907 return pk;
908}