MQTTSuite
Loading...
Searching...
No Matches
mqtt::mqttstore::lib::MariaDbStorage Class Reference

#include <MariaDbStorage.h>

Collaboration diagram for mqtt::mqttstore::lib::MariaDbStorage:

Classes

struct  ConnectionConfig

Public Member Functions

 MariaDbStorage (const std::string &connectionName, const ConnectionConfig &connectionConfig, std::string rawTable, bool autoCreateRawTable, StoragePlan storagePlan)
void store (const MqttMessage &message)

Private Member Functions

void createRawTable ()
void storeProjections (const MqttMessage &message, const std::optional< nlohmann::json > &payloadJson)

Static Private Member Functions

static bool isSafeIdentifier (const std::string &identifier)
static std::string quoteIdentifier (const std::string &identifier)
static std::string sqlQuote (const std::string &value)
static std::string sqlValue (const nlohmann::json &value)
static std::optional< nlohmann::json > parsePayload (const std::string &payload)
static bool hasBinaryContent (const std::string &payload)
static std::string buildRawInsertSql (const std::string &rawTable, const MqttMessage &message, const std::optional< nlohmann::json > &payloadJson)
static std::string buildProjectionInsertSql (const StoragePlan::Projection &projection, const MqttMessage &message, const nlohmann::json &payloadJson)
static std::string jsonValueForColumn (const StoragePlan::ColumnMapping &mapping, const MqttMessage &message, const nlohmann::json &payloadJson)
static void execLogFailure (const std::string &connectionName, const std::string &operation, const std::string &errorString, unsigned int errorNumber)

Private Attributes

std::string connectionName
database::mariadb::MariaDBClient mariaDB
std::string rawTable
StoragePlan storagePlan

Detailed Description

Definition at line 34 of file MariaDbStorage.h.

Constructor & Destructor Documentation

◆ MariaDbStorage()

mqtt::mqttstore::lib::MariaDbStorage::MariaDbStorage ( const std::string & connectionName,
const ConnectionConfig & connectionConfig,
std::string rawTable,
bool autoCreateRawTable,
StoragePlan storagePlan )

Definition at line 55 of file MariaDbStorage.cpp.

61 , mariaDB(
62 {
63 .connectionName = connectionName,
64 .hostname = connectionConfig.host,
65 .username = connectionConfig.username,
66 .password = connectionConfig.password,
67 .database = connectionConfig.database,
68 .port = connectionConfig.port,
69 .socket = connectionConfig.socket,
70 .flags = connectionConfig.flags,
71 },
72 [connectionName = this->connectionName](const database::mariadb::MariaDBState& state) {
73 if (state.connected) {
74 VLOG(0) << connectionName << " MariaDB: connected";
75 } else if (state.error != 0) {
76 VLOG(0) << connectionName << " MariaDB: " << state.errorMessage << " [" << state.error << "]";
77 } else {
78 VLOG(0) << connectionName << " MariaDB: lost connection";
79 }
80 })
81 , rawTable(std::move(rawTable))
82 , storagePlan(std::move(storagePlan)) {
83 if (!isSafeIdentifier(this->rawTable)) {
84 throw std::runtime_error("Unsafe raw table name: " + this->rawTable);
85 }
86
87 if (autoCreateRawTable) {
89 }
90 }
static bool isSafeIdentifier(const std::string &identifier)
database::mariadb::MariaDBClient mariaDB

References connectionName, and MariaDbStorage().

Referenced by MariaDbStorage().

Here is the call graph for this function:
Here is the caller graph for this function:

Member Function Documentation

◆ buildProjectionInsertSql()

std::string mqtt::mqttstore::lib::MariaDbStorage::buildProjectionInsertSql ( const StoragePlan::Projection & projection,
const MqttMessage & message,
const nlohmann::json & payloadJson )
staticnodiscardprivate

Definition at line 209 of file MariaDbStorage.cpp.

211 {
212 std::string columnList;
213 std::string valueList;
214
215 for (const StoragePlan::ColumnMapping& mapping : projection.columns) {
216 const std::string value = jsonValueForColumn(mapping, message, payloadJson);
217 if (value.empty() && !mapping.required) {
218 continue;
219 }
220
221 columnList += (columnList.empty() ? "" : ", ") + quoteIdentifier(mapping.column);
222 valueList += (valueList.empty() ? "" : ", ") + (value.empty() ? "NULL" : value);
223 }
224
225 if (columnList.empty()) {
226 throw std::runtime_error("Projection '" + projection.name + "' produced no columns");
227 }
228
229 return "INSERT INTO " + quoteIdentifier(projection.table) + "(" + columnList + ") VALUES (" + valueList + ")";
230 }
static std::string quoteIdentifier(const std::string &identifier)
static std::string jsonValueForColumn(const StoragePlan::ColumnMapping &mapping, const MqttMessage &message, const nlohmann::json &payloadJson)

References mqtt::mqttstore::lib::StoragePlan::Projection::name, quoteIdentifier(), and mqtt::mqttstore::lib::StoragePlan::Projection::table.

Here is the call graph for this function:

◆ buildRawInsertSql()

std::string mqtt::mqttstore::lib::MariaDbStorage::buildRawInsertSql ( const std::string & rawTable,
const MqttMessage & message,
const std::optional< nlohmann::json > & payloadJson )
staticnodiscardprivate

Definition at line 192 of file MariaDbStorage.cpp.

194 {
195 const bool binaryPayload = !payloadJson.has_value() && hasBinaryContent(message.payload);
196 const std::string payloadFormat = payloadJson.has_value() ? "json" : (binaryPayload ? "binary" : "text");
197 const std::string payloadTextValue = binaryPayload ? "NULL" : sqlQuote(message.payload);
198 const std::string payloadJsonValue = payloadJson.has_value() ? sqlQuote(payloadJson->dump()) : "NULL";
199
200 return "INSERT INTO " + quoteIdentifier(rawTable) +
201 "(`received_at`, `source_instance`, `topic`, `qos`, `retain_flag`, `dup_flag`, `packet_identifier`, `payload`, "
202 "`payload_text`, `payload_json`, `payload_format`) VALUES (CURRENT_TIMESTAMP(6), " +
203 sqlQuote(message.connectionName) + ", " + sqlQuote(message.topic) + ", " +
204 std::to_string(static_cast<unsigned int>(message.qoS)) + ", " + (message.retain ? "TRUE" : "FALSE") + ", " +
205 (message.dup ? "TRUE" : "FALSE") + ", " + std::to_string(message.packetIdentifier) + ", " + sqlQuote(message.payload) +
206 ", " + payloadTextValue + ", " + payloadJsonValue + ", " + sqlQuote(payloadFormat) + ")";
207 }
static bool hasBinaryContent(const std::string &payload)
static std::string sqlQuote(const std::string &value)

References mqtt::mqttstore::lib::MqttMessage::connectionName, mqtt::mqttstore::lib::MqttMessage::dup, hasBinaryContent(), mqtt::mqttstore::lib::MqttMessage::packetIdentifier, mqtt::mqttstore::lib::MqttMessage::payload, mqtt::mqttstore::lib::MqttMessage::qoS, quoteIdentifier(), mqtt::mqttstore::lib::MqttMessage::retain, sqlQuote(), and mqtt::mqttstore::lib::MqttMessage::topic.

Referenced by store().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ createRawTable()

void mqtt::mqttstore::lib::MariaDbStorage::createRawTable ( )
private

Definition at line 267 of file MariaDbStorage.cpp.

267 {
268 const std::string sql = "CREATE TABLE IF NOT EXISTS " + quoteIdentifier(rawTable) +
269 "("
270 "`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,"
271 "`received_at` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),"
272 "`source_instance` VARCHAR(255) NULL,"
273 "`topic` VARCHAR(1024) NOT NULL,"
274 "`qos` TINYINT UNSIGNED NOT NULL,"
275 "`retain_flag` BOOLEAN NOT NULL,"
276 "`dup_flag` BOOLEAN NOT NULL,"
277 "`packet_identifier` INT UNSIGNED NULL,"
278 "`payload` LONGBLOB NOT NULL,"
279 "`payload_text` LONGTEXT NULL,"
280 "`payload_json` JSON NULL,"
281 "`payload_format` ENUM('json', 'text', 'binary') NOT NULL,"
282 "INDEX `idx_received_at` (`received_at`),"
283 "INDEX `idx_topic` (`topic`(255))"
284 ")";
285
286 mariaDB.exec(
287 sql,
288 [connectionName = this->connectionName, rawTable = this->rawTable]() -> void {
289 VLOG(0) << connectionName << " MariaDB: ensured raw MQTT table '" << rawTable << "'";
290 },
291 [connectionName = this->connectionName](const std::string& errorString, unsigned int errorNumber) -> void {
292 execLogFailure(connectionName, "raw MQTT table creation", errorString, errorNumber);
293 });
294 }
static void execLogFailure(const std::string &connectionName, const std::string &operation, const std::string &errorString, unsigned int errorNumber)

References quoteIdentifier(), and rawTable.

Here is the call graph for this function:

◆ execLogFailure()

void mqtt::mqttstore::lib::MariaDbStorage::execLogFailure ( const std::string & connectionName,
const std::string & operation,
const std::string & errorString,
unsigned int errorNumber )
staticprivate

Definition at line 260 of file MariaDbStorage.cpp.

263 {
264 VLOG(0) << connectionName << " MariaDB " << operation << " failed: " << errorString << " : " << errorNumber;
265 }

◆ hasBinaryContent()

bool mqtt::mqttstore::lib::MariaDbStorage::hasBinaryContent ( const std::string & payload)
staticnodiscardprivate

Definition at line 186 of file MariaDbStorage.cpp.

186 {
187 return std::any_of(payload.begin(), payload.end(), [](unsigned char character) {
188 return character == '\0' || (character < 0x09) || (character > 0x0D && character < 0x20);
189 });
190 }

Referenced by buildRawInsertSql().

Here is the caller graph for this function:

◆ isSafeIdentifier()

bool mqtt::mqttstore::lib::MariaDbStorage::isSafeIdentifier ( const std::string & identifier)
staticnodiscardprivate

Definition at line 108 of file MariaDbStorage.cpp.

108 {
109 return !identifier.empty() && std::all_of(identifier.begin(), identifier.end(), [](unsigned char character) {
110 return std::isalnum(character) != 0 || character == '_';
111 });
112 }

Referenced by quoteIdentifier().

Here is the caller graph for this function:

◆ jsonValueForColumn()

std::string mqtt::mqttstore::lib::MariaDbStorage::jsonValueForColumn ( const StoragePlan::ColumnMapping & mapping,
const MqttMessage & message,
const nlohmann::json & payloadJson )
staticnodiscardprivate

Definition at line 232 of file MariaDbStorage.cpp.

234 {
235 if (mapping.literal.has_value()) {
236 return sqlQuote(*mapping.literal);
237 }
238
239 if (mapping.topicLevel.has_value()) {
240 const std::vector<std::string> topicLevels = splitTopic(message.topic);
241 if (*mapping.topicLevel >= topicLevels.size()) {
242 return {};
243 }
244
245 return sqlQuote(topicLevels[*mapping.topicLevel]);
246 }
247
248 if (mapping.jsonPointer.empty()) {
249 return {};
250 }
251
252 const nlohmann::json::json_pointer pointer(mapping.jsonPointer);
253 if (!payloadJson.contains(pointer)) {
254 return {};
255 }
256
257 return sqlValue(payloadJson.at(pointer));
258 }
static std::string sqlValue(const nlohmann::json &value)
std::vector< std::string > splitTopic(const std::string &topic)

References mqtt::mqttstore::lib::StoragePlan::ColumnMapping::jsonPointer, mqtt::mqttstore::lib::StoragePlan::ColumnMapping::literal, sqlQuote(), sqlValue(), and mqtt::mqttstore::lib::StoragePlan::ColumnMapping::topicLevel.

Here is the call graph for this function:

◆ parsePayload()

std::optional< nlohmann::json > mqtt::mqttstore::lib::MariaDbStorage::parsePayload ( const std::string & payload)
staticnodiscardprivate

Definition at line 178 of file MariaDbStorage.cpp.

178 {
179 try {
180 return nlohmann::json::parse(payload);
181 } catch (const nlohmann::json::parse_error&) {
182 return std::nullopt;
183 }
184 }

◆ quoteIdentifier()

std::string mqtt::mqttstore::lib::MariaDbStorage::quoteIdentifier ( const std::string & identifier)
staticnodiscardprivate

Definition at line 114 of file MariaDbStorage.cpp.

114 {
115 if (!isSafeIdentifier(identifier)) {
116 throw std::runtime_error("Unsafe SQL identifier: " + identifier);
117 }
118
119 return "`" + identifier + "`";
120 }

References isSafeIdentifier().

Referenced by buildProjectionInsertSql(), buildRawInsertSql(), and createRawTable().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sqlQuote()

std::string mqtt::mqttstore::lib::MariaDbStorage::sqlQuote ( const std::string & value)
staticnodiscardprivate

Definition at line 122 of file MariaDbStorage.cpp.

122 {
123 std::string quoted;
124 quoted.reserve(value.size() + 2);
125 quoted.push_back('\'');
126
127 for (const char character : value) {
128 switch (character) {
129 case '\0':
130 quoted += "\\0";
131 break;
132 case '\n':
133 quoted += "\\n";
134 break;
135 case '\r':
136 quoted += "\\r";
137 break;
138 case '\\':
139 quoted += "\\\\";
140 break;
141 case '\'':
142 quoted += "\\'";
143 break;
144 case '"':
145 quoted += "\\\"";
146 break;
147 case '\x1a':
148 quoted += "\\Z";
149 break;
150 default:
151 quoted.push_back(character);
152 break;
153 }
154 }
155
156 quoted.push_back('\'');
157
158 return quoted;
159 }

Referenced by buildRawInsertSql(), jsonValueForColumn(), and sqlValue().

Here is the caller graph for this function:

◆ sqlValue()

std::string mqtt::mqttstore::lib::MariaDbStorage::sqlValue ( const nlohmann::json & value)
staticnodiscardprivate

Definition at line 161 of file MariaDbStorage.cpp.

161 {
162 if (value.is_null()) {
163 return "NULL";
164 }
165 if (value.is_boolean()) {
166 return value.get<bool>() ? "TRUE" : "FALSE";
167 }
168 if (value.is_number()) {
169 return value.dump();
170 }
171 if (value.is_string()) {
172 return sqlQuote(value.get<std::string>());
173 }
174
175 return sqlQuote(value.dump());
176 }

References sqlQuote().

Referenced by jsonValueForColumn().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ store()

void mqtt::mqttstore::lib::MariaDbStorage::store ( const MqttMessage & message)

Definition at line 92 of file MariaDbStorage.cpp.

92 {
93 const std::optional<nlohmann::json> payloadJson = parsePayload(message.payload);
94 const std::string rawInsertSql = buildRawInsertSql(rawTable, message, payloadJson);
95
96 mariaDB.exec(
97 rawInsertSql,
98 [connectionName = this->connectionName, topic = message.topic]() -> void {
99 VLOG(1) << connectionName << " MariaDB: stored raw MQTT message for topic '" << topic << "'";
100 },
101 [connectionName = this->connectionName](const std::string& errorString, unsigned int errorNumber) -> void {
102 execLogFailure(connectionName, "raw MQTT message insert", errorString, errorNumber);
103 });
104
105 storeProjections(message, payloadJson);
106 }
static std::string buildRawInsertSql(const std::string &rawTable, const MqttMessage &message, const std::optional< nlohmann::json > &payloadJson)
void storeProjections(const MqttMessage &message, const std::optional< nlohmann::json > &payloadJson)
static std::optional< nlohmann::json > parsePayload(const std::string &payload)

References buildRawInsertSql(), rawTable, and storeProjections().

Here is the call graph for this function:

◆ storeProjections()

void mqtt::mqttstore::lib::MariaDbStorage::storeProjections ( const MqttMessage & message,
const std::optional< nlohmann::json > & payloadJson )
private

Definition at line 296 of file MariaDbStorage.cpp.

296 {
297 if (!payloadJson.has_value()) {
298 return;
299 }
300
301 for (const StoragePlan::Projection* projection : storagePlan.match(message.topic)) {
302 try {
303 const std::string sql = buildProjectionInsertSql(*projection, message, *payloadJson);
304 mariaDB.exec(
305 sql,
306 [connectionName = this->connectionName, projectionName = projection->name]() -> void {
307 VLOG(1) << connectionName << " MariaDB: projection insert completed for '" << projectionName << "'";
308 },
309 [connectionName = this->connectionName, projectionName = projection->name](const std::string& errorString,
310 unsigned int errorNumber) -> void {
311 execLogFailure(connectionName, "projection '" + projectionName + "' insert", errorString, errorNumber);
312 });
313 } catch (const std::exception& error) {
314 VLOG(0) << connectionName << " MariaDB projection '" << projection->name << "' skipped: " << error.what();
315 }
316 }
317 }
static std::string buildProjectionInsertSql(const StoragePlan::Projection &projection, const MqttMessage &message, const nlohmann::json &payloadJson)

Referenced by store().

Here is the caller graph for this function:

Member Data Documentation

◆ connectionName

std::string mqtt::mqttstore::lib::MariaDbStorage::connectionName
private

Definition at line 74 of file MariaDbStorage.h.

Referenced by MariaDbStorage().

◆ mariaDB

database::mariadb::MariaDBClient mqtt::mqttstore::lib::MariaDbStorage::mariaDB
private

Definition at line 75 of file MariaDbStorage.h.

◆ rawTable

std::string mqtt::mqttstore::lib::MariaDbStorage::rawTable
private

Definition at line 76 of file MariaDbStorage.h.

Referenced by createRawTable(), and store().

◆ storagePlan

StoragePlan mqtt::mqttstore::lib::MariaDbStorage::storagePlan
private

Definition at line 77 of file MariaDbStorage.h.


The documentation for this class was generated from the following files: