1133 {
1135 return VoidResult(kcenon::common::error_info{
1136 -1, "Database not initialized", "prefetch_repository"});
1137 }
1138
1139 static constexpr const char* sql = R"(
1140 INSERT INTO prefetch_rules (
1141 rule_id, name, enabled, trigger_type,
1142 modality_filter, body_part_filter, station_ae_filter,
1143 prior_lookback_hours, max_prior_studies, prior_modalities_json,
1144 source_node_ids_json, schedule_cron, advance_time_minutes,
1145 triggered_count, studies_prefetched, last_triggered
1146 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1147 ON CONFLICT(rule_id) DO UPDATE SET
1148 name = excluded.name,
1149 enabled = excluded.enabled,
1150 trigger_type = excluded.trigger_type,
1151 modality_filter = excluded.modality_filter,
1152 body_part_filter = excluded.body_part_filter,
1153 station_ae_filter = excluded.station_ae_filter,
1154 prior_lookback_hours = excluded.prior_lookback_hours,
1155 max_prior_studies = excluded.max_prior_studies,
1156 prior_modalities_json = excluded.prior_modalities_json,
1157 source_node_ids_json = excluded.source_node_ids_json,
1158 schedule_cron = excluded.schedule_cron,
1159 advance_time_minutes = excluded.advance_time_minutes,
1160 updated_at = CURRENT_TIMESTAMP
1161 )";
1162
1163 sqlite3_stmt* stmt = nullptr;
1164 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
1165 return VoidResult(kcenon::common::error_info{
1166 -1,
"Failed to prepare statement: " + std::string(sqlite3_errmsg(
db_)),
1167 "prefetch_repository"});
1168 }
1169
1170 int idx = 1;
1171 sqlite3_bind_text(stmt, idx++, rule.rule_id.c_str(), -1, SQLITE_TRANSIENT);
1172 sqlite3_bind_text(stmt, idx++, rule.name.c_str(), -1, SQLITE_TRANSIENT);
1173 sqlite3_bind_int(stmt, idx++, rule.enabled ? 1 : 0);
1174 sqlite3_bind_text(stmt, idx++,
client::to_string(rule.trigger), -1, SQLITE_STATIC);
1175
1176 if (rule.modality_filter.empty()) {
1177 sqlite3_bind_null(stmt, idx++);
1178 } else {
1179 sqlite3_bind_text(stmt, idx++, rule.modality_filter.c_str(), -1, SQLITE_TRANSIENT);
1180 }
1181
1182 if (rule.body_part_filter.empty()) {
1183 sqlite3_bind_null(stmt, idx++);
1184 } else {
1185 sqlite3_bind_text(stmt, idx++, rule.body_part_filter.c_str(), -1, SQLITE_TRANSIENT);
1186 }
1187
1188 if (rule.station_ae_filter.empty()) {
1189 sqlite3_bind_null(stmt, idx++);
1190 } else {
1191 sqlite3_bind_text(stmt, idx++, rule.station_ae_filter.c_str(), -1, SQLITE_TRANSIENT);
1192 }
1193
1194 sqlite3_bind_int64(stmt, idx++, rule.prior_lookback.count());
1195 sqlite3_bind_int64(stmt, idx++, static_cast<int64_t>(rule.max_prior_studies));
1196
1198 sqlite3_bind_text(stmt, idx++, modalities_json.c_str(), -1, SQLITE_TRANSIENT);
1199
1201 sqlite3_bind_text(stmt, idx++, node_ids_json.c_str(), -1, SQLITE_TRANSIENT);
1202
1203 if (rule.schedule_cron.empty()) {
1204 sqlite3_bind_null(stmt, idx++);
1205 } else {
1206 sqlite3_bind_text(stmt, idx++, rule.schedule_cron.c_str(), -1, SQLITE_TRANSIENT);
1207 }
1208
1209 sqlite3_bind_int64(stmt, idx++, rule.advance_time.count());
1210 sqlite3_bind_int64(stmt, idx++, static_cast<int64_t>(rule.triggered_count));
1211 sqlite3_bind_int64(stmt, idx++, static_cast<int64_t>(rule.studies_prefetched));
1212
1213 auto last_triggered_str = to_timestamp_string(rule.last_triggered);
1214 if (last_triggered_str.empty()) {
1215 sqlite3_bind_null(stmt, idx++);
1216 } else {
1217 sqlite3_bind_text(stmt, idx++, last_triggered_str.c_str(), -1, SQLITE_TRANSIENT);
1218 }
1219
1220 auto rc = sqlite3_step(stmt);
1221 sqlite3_finalize(stmt);
1222
1223 if (rc != SQLITE_DONE) {
1224 return VoidResult(kcenon::common::error_info{
1225 -1,
"Failed to save rule: " + std::string(sqlite3_errmsg(
db_)),
1226 "prefetch_repository"});
1227 }
1228
1229 return kcenon::common::ok();
1230}
static auto serialize_node_ids(const std::vector< std::string > &node_ids) -> std::string
static auto serialize_modalities(const std::vector< std::string > &modalities) -> std::string