1074 {
1076 return VoidResult(kcenon::common::error_info{
1077 -1, "Database not initialized", "routing_repository"});
1078 }
1079
1080 static constexpr const char* sql = R"(
1081 INSERT INTO routing_rules (
1082 rule_id, name, description, enabled, priority,
1083 conditions_json, actions_json,
1084 schedule_cron, effective_from, effective_until,
1085 triggered_count, success_count, failure_count,
1086 last_triggered, created_at, updated_at
1087 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1088 ON CONFLICT(rule_id) DO UPDATE SET
1089 name = excluded.name,
1090 description = excluded.description,
1091 enabled = excluded.enabled,
1092 priority = excluded.priority,
1093 conditions_json = excluded.conditions_json,
1094 actions_json = excluded.actions_json,
1095 schedule_cron = excluded.schedule_cron,
1096 effective_from = excluded.effective_from,
1097 effective_until = excluded.effective_until,
1098 updated_at = CURRENT_TIMESTAMP
1099 )";
1100
1101 sqlite3_stmt* stmt = nullptr;
1102 if (sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr) != SQLITE_OK) {
1103 return VoidResult(kcenon::common::error_info{
1104 -1,
"Failed to prepare statement: " + std::string(sqlite3_errmsg(
db_)),
1105 "routing_repository"});
1106 }
1107
1108 int idx = 1;
1109 sqlite3_bind_text(stmt, idx++, rule.rule_id.c_str(), -1, SQLITE_TRANSIENT);
1110 sqlite3_bind_text(stmt, idx++, rule.name.c_str(), -1, SQLITE_TRANSIENT);
1111 sqlite3_bind_text(stmt, idx++, rule.description.c_str(), -1, SQLITE_TRANSIENT);
1112 sqlite3_bind_int(stmt, idx++, rule.enabled ? 1 : 0);
1113 sqlite3_bind_int(stmt, idx++, rule.priority);
1114
1116 sqlite3_bind_text(stmt, idx++, conditions_json.c_str(), -1, SQLITE_TRANSIENT);
1117
1119 sqlite3_bind_text(stmt, idx++, actions_json.c_str(), -1, SQLITE_TRANSIENT);
1120
1121 bind_optional_text(stmt, idx++, rule.schedule_cron);
1122 bind_optional_timestamp(stmt, idx++, rule.effective_from);
1123 bind_optional_timestamp(stmt, idx++, rule.effective_until);
1124
1125 sqlite3_bind_int64(stmt, idx++, static_cast<int64_t>(rule.triggered_count));
1126 sqlite3_bind_int64(stmt, idx++, static_cast<int64_t>(rule.success_count));
1127 sqlite3_bind_int64(stmt, idx++, static_cast<int64_t>(rule.failure_count));
1128
1129 auto last_triggered_str = to_timestamp_string(rule.last_triggered);
1130 if (last_triggered_str.empty()) {
1131 sqlite3_bind_null(stmt, idx++);
1132 } else {
1133 sqlite3_bind_text(stmt, idx++, last_triggered_str.c_str(), -1, SQLITE_TRANSIENT);
1134 }
1135
1136 auto created_str = to_timestamp_string(rule.created_at);
1137 if (created_str.empty()) {
1138 sqlite3_bind_text(stmt, idx++, "CURRENT_TIMESTAMP", -1, SQLITE_STATIC);
1139 } else {
1140 sqlite3_bind_text(stmt, idx++, created_str.c_str(), -1, SQLITE_TRANSIENT);
1141 }
1142
1143 auto updated_str = to_timestamp_string(rule.updated_at);
1144 if (updated_str.empty()) {
1145 sqlite3_bind_text(stmt, idx++, "CURRENT_TIMESTAMP", -1, SQLITE_STATIC);
1146 } else {
1147 sqlite3_bind_text(stmt, idx++, updated_str.c_str(), -1, SQLITE_TRANSIENT);
1148 }
1149
1150 auto rc = sqlite3_step(stmt);
1151 sqlite3_finalize(stmt);
1152
1153 if (rc != SQLITE_DONE) {
1154 return VoidResult(kcenon::common::error_info{
1155 -1,
"Failed to save rule: " + std::string(sqlite3_errmsg(
db_)),
1156 "routing_repository"});
1157 }
1158
1159 return kcenon::common::ok();
1160}
static auto serialize_conditions(const std::vector< client::routing_condition > &conditions) -> std::string
static auto serialize_actions(const std::vector< client::routing_action > &actions) -> std::string