db: Track app launch history with sqlite db
Add support for sqlite3 database. Track the application launches'
metrics by inserting new rows into the database.
Test: manual (launch app and then check /data/misc/iorapd/sqlite.db)
Bug: 137786053
Change-Id: I0fa337926a4aad05495b00ebee6a0b3e815d6af7
diff --git a/src/db/models.h b/src/db/models.h
new file mode 100644
index 0000000..ed6c16d
--- /dev/null
+++ b/src/db/models.h
@@ -0,0 +1,760 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef IORAP_SRC_DB_MODELS_H_
+#define IORAP_SRC_DB_MODELS_H_
+
+#include <android-base/logging.h>
+
+#include <optional>
+#include <ostream>
+#include <string>
+#include <sstream>
+#include <type_traits>
+
+#include <sqlite3.h>
+
+namespace iorap::db {
+
+struct SqliteDbDeleter {
+ void operator()(sqlite3* db) {
+ if (db != nullptr) {
+ LOG(VERBOSE) << "sqlite3_close for: " << db;
+ sqlite3_close(db);
+ }
+ }
+};
+
+class DbHandle {
+ public:
+ // Take over ownership of sqlite3 db.
+ explicit DbHandle(sqlite3* db)
+ : db_{std::shared_ptr<sqlite3>{db, SqliteDbDeleter{}}},
+ mutex_{std::make_shared<std::mutex>()} {
+ }
+
+ sqlite3* get() {
+ return db_.get();
+ }
+
+ operator sqlite3*() {
+ return db_.get();
+ }
+
+ std::mutex& mutex() {
+ return *mutex_.get();
+ }
+
+ private:
+ std::shared_ptr<sqlite3> db_;
+ std::shared_ptr<std::mutex> mutex_;
+};
+
+class ScopedLockDb {
+ public:
+ ScopedLockDb(std::mutex& mutex) : mutex(mutex) {
+ mutex.lock();
+ }
+
+ ScopedLockDb(DbHandle& handle) : ScopedLockDb(handle.mutex()) {
+ }
+
+ ~ScopedLockDb() {
+ mutex.unlock();
+ }
+ private:
+ std::mutex& mutex;
+};
+
+class DbStatement {
+ public:
+ template <typename ... Args>
+ static DbStatement Prepare(DbHandle db, const std::string& sql, Args&&... args) {
+ return Prepare(db, sql.c_str(), std::forward<Args>(args)...);
+ }
+
+ template <typename ... Args>
+ static DbStatement Prepare(DbHandle db, const char* sql, Args&&... args) {
+ DCHECK(db.get() != nullptr);
+ DCHECK(sql != nullptr);
+
+ // LOG(VERBOSE) << "Prepare DB=" << db.get();
+
+ sqlite3_stmt* stmt = nullptr;
+ int rc = sqlite3_prepare_v2(db.get(), sql, -1, /*out*/&stmt, nullptr);
+
+ DbStatement db_stmt{db, stmt};
+ DCHECK(db_stmt.CheckOk(rc)) << sql;
+ db_stmt.BindAll(std::forward<Args>(args)...);
+
+ return db_stmt;
+ }
+
+ DbStatement(DbHandle db, sqlite3_stmt* stmt) : db_(db), stmt_(stmt) {
+ }
+
+ sqlite3_stmt* get() {
+ return stmt_;
+ }
+
+ DbHandle db() {
+ return db_;
+ }
+
+ template <typename T, typename ... Args>
+ void BindAll(T&& arg, Args&&... args) {
+ Bind(std::forward<T>(arg));
+ BindAll(std::forward<Args>(args)...);
+ }
+
+ void BindAll() {}
+
+ template <typename T>
+ void Bind(const std::optional<T>& value) {
+ if (value) {
+ Bind(*value);
+ } else {
+ BindNull();
+ }
+ }
+
+ void Bind(bool value) {
+ CheckOk(sqlite3_bind_int(stmt_, bind_counter_++, value));
+ }
+
+ void Bind(int value) {
+ CheckOk(sqlite3_bind_int(stmt_, bind_counter_++, value));
+ }
+
+ void Bind(uint64_t value) {
+ CheckOk(sqlite3_bind_int64(stmt_, bind_counter_++, static_cast<int64_t>(value)));
+ }
+
+ void Bind(const char* value) {
+ if (value != nullptr) {
+ //sqlite3_bind_text(stmt_, /*index*/bind_counter_++, value, -1, SQLITE_STATIC);
+ CheckOk(sqlite3_bind_text(stmt_, /*index*/bind_counter_++, value, -1, SQLITE_TRANSIENT));
+ } else {
+ BindNull();
+ }
+ }
+
+ void Bind(const std::string& value) {
+ Bind(value.c_str());
+ }
+
+ template <typename E, typename = std::enable_if_t<std::is_enum_v<E>>>
+ void Bind(E value) {
+ Bind(static_cast<std::underlying_type_t<E>>(value));
+ }
+
+ void BindNull() {
+ CheckOk(sqlite3_bind_null(stmt_, bind_counter_++));
+ }
+
+ int Step() {
+ ++step_counter_;
+ return sqlite3_step(stmt_);
+ }
+
+ bool Step(int expected) {
+ int rc = Step();
+ if (rc != expected) {
+ LOG(ERROR) << "SQLite error: " << sqlite3_errmsg(db_.get());
+ return false;
+ }
+ return true;
+ }
+
+ template <typename T, typename ... Args>
+ void ColumnAll(T& first, Args&... rest) {
+ Column(first);
+ ColumnAll(rest...);
+ }
+
+ void ColumnAll() {}
+
+ template <typename T>
+ void Column(std::optional<T>& value) {
+ T tmp;
+ Column(/*out*/tmp);
+
+ if (!tmp) { // disambiguate 0 and NULL
+ const unsigned char* text = sqlite3_column_text(stmt_, column_counter_ - 1);
+ if (text == nullptr) {
+ value = std::nullopt;
+ return;
+ }
+ }
+ value = std::move(tmp);
+ }
+
+ template <typename E, typename = std::enable_if_t<std::is_enum_v<E>>>
+ void Column(E& value) {
+ std::underlying_type_t<E> tmp;
+ Column(/*out*/tmp);
+ value = static_cast<E>(tmp);
+ }
+
+ void Column(bool& value) {
+ value = sqlite3_column_int(stmt_, column_counter_++);
+ }
+
+ void Column(int& value) {
+ value = sqlite3_column_int(stmt_, column_counter_++);
+ }
+
+ void Column(uint64_t& value) {
+ value = static_cast<uint64_t>(sqlite3_column_int64(stmt_, column_counter_++));
+ }
+
+ void Column(std::string& value) {
+ const unsigned char* text = sqlite3_column_text(stmt_, column_counter_++);
+ value = std::string{reinterpret_cast<const char*>(text)};
+ }
+
+ const char* ExpandedSql() {
+ char* p = sqlite3_expanded_sql(stmt_);
+ if (p == nullptr) {
+ return "(nullptr)";
+ }
+ return p;
+ }
+
+ const char* Sql() {
+ const char* p = sqlite3_sql(stmt_);
+ if (p == nullptr) {
+ return "(nullptr)";
+ }
+ return p;
+ }
+
+
+ DbStatement(DbStatement&& other)
+ : db_{other.db_}, stmt_{other.stmt_}, bind_counter_{other.bind_counter_},
+ step_counter_{other.step_counter_} {
+ other.db_ = DbHandle{nullptr};
+ other.stmt_ = nullptr;
+ }
+
+ ~DbStatement() {
+ if (stmt_ != nullptr) {
+ DCHECK_GT(step_counter_, 0) << " forgot to call Step()?";
+ sqlite3_finalize(stmt_);
+ }
+ }
+
+ private:
+ bool CheckOk(int rc, int expected = SQLITE_OK) {
+ if (rc != expected) {
+ LOG(ERROR) << "Got error for SQL query: '" << Sql() << "'"
+ << ", expanded: '" << ExpandedSql() << "'";
+ LOG(ERROR) << "Failed SQLite api call (" << rc << "): " << sqlite3_errstr(rc);
+ }
+ return rc == expected;
+ }
+
+ DbHandle db_;
+ sqlite3_stmt* stmt_;
+ int bind_counter_ = 1;
+ int step_counter_ = 0;
+ int column_counter_ = 0;
+};
+
+class DbQueryBuilder {
+ public:
+ // Returns the row ID that was inserted last.
+ template <typename... Args>
+ static std::optional<int> Insert(DbHandle db, const std::string& sql, Args&&... args) {
+ ScopedLockDb lock{db};
+
+ sqlite3_int64 last_rowid = sqlite3_last_insert_rowid(db.get());
+ DbStatement stmt = DbStatement::Prepare(db, sql, std::forward<Args>(args)...);
+
+ if (!stmt.Step(SQLITE_DONE)) {
+ return std::nullopt;
+ }
+
+ last_rowid = sqlite3_last_insert_rowid(db.get());
+ DCHECK_GT(last_rowid, 0);
+
+ return static_cast<int>(last_rowid);
+ }
+
+ template <typename... Args>
+ static bool SelectOnce(DbStatement& stmt, Args&... args) {
+ int rc = stmt.Step();
+ switch (rc) {
+ case SQLITE_ROW:
+ stmt.ColumnAll(/*out*/args...);
+ return true;
+ case SQLITE_DONE:
+ return false;
+ default:
+ LOG(ERROR) << "Failed to step (" << rc << "): " << sqlite3_errmsg(stmt.db());
+ return false;
+ }
+ }
+};
+
+class Model {
+ public:
+ DbHandle db() const {
+ return db_;
+ }
+
+ Model(DbHandle db) : db_{db} {
+ }
+
+ private:
+ DbHandle db_;
+};
+
+class SchemaModel : public Model {
+ public:
+ static SchemaModel GetOrCreate(std::string location) {
+ int rc = sqlite3_config(SQLITE_CONFIG_LOG, ErrorLogCallback, /*data*/nullptr);
+
+ if (rc != SQLITE_OK) {
+ LOG(FATAL) << "Failed to configure logging";
+ }
+
+ sqlite3* db = nullptr;
+ if (location != ":memory:") {
+ // Try to open DB if it already exists.
+ rc = sqlite3_open_v2(location.c_str(), /*out*/&db, SQLITE_OPEN_READWRITE, /*vfs*/nullptr);
+
+ if (rc == SQLITE_OK) {
+ LOG(INFO) << "Opened existing database at '" << location << "'";
+ return SchemaModel{DbHandle{db}, location};
+ }
+ }
+
+ // Create a new DB if one didn't exist already.
+ rc = sqlite3_open(location.c_str(), /*out*/&db);
+
+ if (rc != SQLITE_OK) {
+ LOG(FATAL) << "Failed to open DB: " << sqlite3_errmsg(db);
+ }
+
+ SchemaModel schema{DbHandle{db}, location};
+ schema.Reinitialize();
+ // TODO: migrate versions upwards when we rev the schema version
+
+ int old_version = schema.Version();
+ LOG(VERBOSE) << "Loaded schema version: " << old_version;
+
+ return schema;
+ }
+
+ void MarkSingleton() {
+ s_singleton_ = db();
+ }
+
+ static DbHandle GetSingleton() {
+ DCHECK(s_singleton_.has_value());
+ return *s_singleton_;
+ }
+
+ void Reinitialize() {
+ const char* sql_to_initialize = R"SQLC0D3(
+ DROP TABLE IF EXISTS schema_versions;
+ DROP TABLE IF EXISTS packages;
+ DROP TABLE IF EXISTS activities;
+ DROP TABLE IF EXISTS app_launch_histories;
+ DROP TABLE IF EXISTS raw_traces;
+ DROP TABLE IF EXISTS prefetch_files;
+)SQLC0D3";
+ char* err_msg = nullptr;
+ int rc = sqlite3_exec(db().get(),
+ sql_to_initialize,
+ /*callback*/nullptr,
+ /*arg*/0,
+ /*out*/&err_msg);
+ if (rc != SQLITE_OK) {
+ LOG(FATAL) << "Failed to drop tables: " << err_msg ? err_msg : "nullptr";
+ }
+
+ CreateSchema();
+ LOG(INFO) << "Reinitialized database at '" << location_ << "'";
+ }
+
+ int Version() {
+ std::string query = "SELECT MAX(version) FROM schema_versions;";
+ DbStatement stmt = DbStatement::Prepare(db(), query);
+
+ int return_value = 0;
+ if (!DbQueryBuilder::SelectOnce(stmt, /*out*/return_value)) {
+ LOG(ERROR) << "Failed to query schema version";
+ return -1;
+ }
+
+ return return_value;
+ }
+
+ protected:
+ SchemaModel(DbHandle db, std::string location) : Model{db}, location_(location) {
+ }
+
+ private:
+ static std::optional<DbHandle> s_singleton_;
+
+ void CreateSchema() {
+ const char* sql_to_initialize = R"SQLC0D3(
+ CREATE TABLE schema_versions(
+ version INTEGER NOT NULL
+ );
+ INSERT INTO schema_versions VALUES(1);
+
+ CREATE TABLE packages(
+ id INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ version INTEGER,
+
+ PRIMARY KEY(id)
+ );
+
+ CREATE TABLE activities(
+ id INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ package_id INTEGER NOT NULL,
+
+ PRIMARY KEY(id),
+ FOREIGN KEY (package_id) REFERENCES packages (id)
+ );
+
+ CREATE TABLE app_launch_histories(
+ id INTEGER NOT NULL PRIMARY KEY,
+ activity_id INTEGER NOT NULL,
+ -- 1:Cold, 2:Warm, 3:Hot
+ temperature INTEGER CHECK (temperature IN (1, 2, 3)) NOT NULL,
+ trace_enabled INTEGER CHECK(trace_enabled in (TRUE, FALSE)) NOT NULL,
+ readahead_enabled INTEGER CHECK(trace_enabled in (TRUE, FALSE)) NOT NULL,
+ -- absolute timestamp since epoch
+ intent_started_ns INTEGER CHECK(intent_started_ns IS NULL or intent_started_ns >= 0),
+ -- absolute timestamp since epoch
+ total_time_ns INTEGER CHECK(total_time_ns IS NULL or total_time_ns >= 0),
+ -- absolute timestamp since epoch
+ report_fully_drawn_ns INTEGER CHECK(report_fully_drawn_ns IS NULL or report_fully_drawn_ns >= 0),
+
+ FOREIGN KEY (activity_id) REFERENCES activities (id)
+ );
+
+ CREATE TABLE raw_traces(
+ id INTEGER NOT NULL PRIMARY KEY,
+ history_id INTEGER NOT NULL,
+ file_path TEXT NOT NULL,
+
+ FOREIGN KEY (history_id) REFERENCES app_launch_histories (id)
+ );
+
+ CREATE TABLE prefetch_files(
+ id INTEGER NOT NULL PRIMARY KEY,
+ activity_id INTEGER NOT NULL,
+ file_path TEXT NOT NULL,
+
+ FOREIGN KEY (activity_id) REFERENCES activities (id)
+ );
+)SQLC0D3";
+
+ char* err_msg = nullptr;
+ int rc = sqlite3_exec(db().get(),
+ sql_to_initialize,
+ /*callback*/nullptr,
+ /*arg*/0,
+ /*out*/&err_msg);
+
+ if (rc != SQLITE_OK) {
+ LOG(FATAL) << "Failed to create tables: " << err_msg ? err_msg : "nullptr";
+ }
+ }
+
+ static void ErrorLogCallback(void *pArg, int iErrCode, const char *zMsg) {
+ LOG(ERROR) << "SQLite error (" << iErrCode << "): " << zMsg;
+ }
+
+ std::string location_;
+};
+
+class PackageModel : public Model {
+ protected:
+ PackageModel(DbHandle db) : Model{db} {
+ }
+
+ public:
+ static std::optional<PackageModel> SelectById(DbHandle db, int id) {
+ ScopedLockDb lock{db};
+ int original_id = id;
+
+ std::string query = "SELECT * FROM packages WHERE id = ?1 LIMIT 1;";
+ DbStatement stmt = DbStatement::Prepare(db, query, id);
+
+ PackageModel p{db};
+ if (!DbQueryBuilder::SelectOnce(stmt, p.id, p.name, p.version)) {
+ return std::nullopt;
+ }
+
+ return p;
+ }
+
+ static std::optional<PackageModel> SelectByName(DbHandle db, const char* name) {
+ ScopedLockDb lock{db};
+
+ std::string query = "SELECT * FROM packages WHERE name = ?1 LIMIT 1;";
+ DbStatement stmt = DbStatement::Prepare(db, query, name);
+
+ PackageModel p{db};
+ if (!DbQueryBuilder::SelectOnce(stmt, p.id, p.name, p.version)) {
+ return std::nullopt;
+ }
+
+ return p;
+ }
+
+ static std::optional<PackageModel> Insert(DbHandle db,
+ std::string name,
+ std::optional<int> version) {
+ const char* sql = "INSERT INTO packages (name, version) VALUES (?1, ?2);";
+
+ std::optional<int> inserted_row_id =
+ DbQueryBuilder::Insert(db, sql, name, version);
+ if (!inserted_row_id) {
+ return std::nullopt;
+ }
+
+ PackageModel p{db};
+ p.name = name;
+ p.version = version;
+ p.id = *inserted_row_id;
+
+ return p;
+ }
+
+ int id;
+ std::string name;
+ std::optional<int> version;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const PackageModel& p) {
+ os << "PackageModel{id=" << p.id << ",name=" << p.name << ",";
+ os << "version=";
+ if (p.version) {
+ os << *p.version;
+ } else {
+ os << "(nullopt)";
+ }
+ os << "}";
+ return os;
+}
+
+class ActivityModel : public Model {
+ protected:
+ ActivityModel(DbHandle db) : Model{db} {
+ }
+
+ public:
+ static std::optional<ActivityModel> SelectById(DbHandle db, int id) {
+ ScopedLockDb lock{db};
+ int original_id = id;
+
+ std::string query = "SELECT * FROM activities WHERE id = ? LIMIT 1;";
+ DbStatement stmt = DbStatement::Prepare(db, query, id);
+
+ ActivityModel p{db};
+ if (!DbQueryBuilder::SelectOnce(stmt, p.id, p.name, p.package_id)) {
+ return std::nullopt;
+ }
+
+ return p;
+ }
+
+ static std::optional<ActivityModel> SelectByNameAndPackageId(DbHandle db,
+ const char* name,
+ int package_id) {
+ ScopedLockDb lock{db};
+
+ std::string query = "SELECT * FROM activities WHERE name = ? AND package_id = ? LIMIT 1;";
+ DbStatement stmt = DbStatement::Prepare(db, query, name, package_id);
+
+ ActivityModel p{db};
+ if (!DbQueryBuilder::SelectOnce(stmt, p.id, p.name, p.package_id)) {
+ return std::nullopt;
+ }
+
+ return p;
+ }
+
+ static std::optional<ActivityModel> Insert(DbHandle db,
+ std::string name,
+ int package_id) {
+ const char* sql = "INSERT INTO activities (name, package_id) VALUES (?1, ?2);";
+
+ std::optional<int> inserted_row_id =
+ DbQueryBuilder::Insert(db, sql, name, package_id);
+ if (!inserted_row_id) {
+ return std::nullopt;
+ }
+
+ ActivityModel p{db};
+ p.id = *inserted_row_id;
+ p.name = name;
+ p.package_id = package_id;
+
+ return p;
+ }
+
+ // Try to select by package_name+activity_name, otherwise insert into both tables.
+ // Package version is ignored for selects.
+ static std::optional<ActivityModel> SelectOrInsert(
+ DbHandle db,
+ std::string package_name,
+ std::optional<int> package_version,
+ std::string activity_name) {
+ std::optional<PackageModel> package = PackageModel::SelectByName(db, package_name.c_str());
+ if (!package) {
+ package = PackageModel::Insert(db, package_name, package_version);
+ DCHECK(package.has_value());
+ }
+
+ std::optional<ActivityModel> activity =
+ ActivityModel::SelectByNameAndPackageId(db,
+ activity_name.c_str(),
+ package->id);
+ if (!activity) {
+ activity = Insert(db, activity_name, package->id);
+ // XX: should we really return an optional here? This feels like it should never fail.
+ }
+
+ return activity;
+ }
+
+ int id;
+ std::string name;
+ int package_id; // PackageModel::id
+};
+
+inline std::ostream& operator<<(std::ostream& os, const ActivityModel& p) {
+ os << "ActivityModel{id=" << p.id << ",name=" << p.name << ",";
+ os << "package_id=" << p.package_id << "}";
+ return os;
+}
+
+class AppLaunchHistoryModel : public Model {
+ protected:
+ AppLaunchHistoryModel(DbHandle db) : Model{db} {
+ }
+
+ public:
+ enum class Temperature : int32_t {
+ kUninitialized = -1, // Note: Not a valid SQL value.
+ kCold = 1,
+ kWarm = 2,
+ kHot = 3,
+ };
+
+ static std::optional<AppLaunchHistoryModel> SelectById(DbHandle db, int id) {
+ ScopedLockDb lock{db};
+ int original_id = id;
+
+ std::string query = "SELECT * FROM app_launch_histories WHERE id = ? LIMIT 1;";
+ DbStatement stmt = DbStatement::Prepare(db, query, id);
+
+ AppLaunchHistoryModel p{db};
+ if (!DbQueryBuilder::SelectOnce(stmt,
+ p.id,
+ p.activity_id,
+ p.temperature,
+ p.trace_enabled,
+ p.readahead_enabled,
+ p.total_time_ns,
+ p.report_fully_drawn_ns)) {
+ return std::nullopt;
+ }
+
+ return p;
+ }
+
+ static std::optional<AppLaunchHistoryModel> Insert(DbHandle db,
+ int activity_id,
+ AppLaunchHistoryModel::Temperature temperature,
+ bool trace_enabled,
+ bool readahead_enabled,
+ std::optional<uint64_t> total_time_ns,
+ std::optional<uint64_t> report_fully_drawn_ns)
+ {
+ const char* sql = "INSERT INTO app_launch_histories (activity_id, temperature, trace_enabled, "
+ "readahead_enabled, total_time_ns, "
+ "report_fully_drawn_ns) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6);";
+
+ std::optional<int> inserted_row_id =
+ DbQueryBuilder::Insert(db,
+ sql,
+ activity_id,
+ temperature,
+ trace_enabled,
+ readahead_enabled,
+ total_time_ns,
+ report_fully_drawn_ns);
+ if (!inserted_row_id) {
+ return std::nullopt;
+ }
+
+ AppLaunchHistoryModel p{db};
+ p.id = *inserted_row_id;
+ p.activity_id = activity_id;
+ p.temperature = temperature;
+ p.trace_enabled = trace_enabled;
+ p.readahead_enabled = readahead_enabled;
+ p.total_time_ns = total_time_ns;
+ p.report_fully_drawn_ns = report_fully_drawn_ns;
+
+ return p;
+ }
+
+ int id;
+ int activity_id; // ActivityModel::id
+ Temperature temperature = Temperature::kUninitialized;
+ bool trace_enabled;
+ bool readahead_enabled;
+ std::optional<uint64_t> total_time_ns;
+ std::optional<uint64_t> report_fully_drawn_ns;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const AppLaunchHistoryModel& p) {
+ os << "AppLaunchHistoryModel{id=" << p.id << ","
+ << "activity_id=" << p.activity_id << ","
+ << "temperature=" << static_cast<int>(p.temperature) << ","
+ << "trace_enabled=" << p.trace_enabled << ","
+ << "readahead_enabled=" << p.readahead_enabled << ","
+ << "total_time_ns=";
+ if (p.total_time_ns) {
+ os << *p.total_time_ns;
+ } else {
+ os << "(nullopt)";
+ }
+ os << ",";
+ os << "report_fully_drawn_ns=";
+ if (p.report_fully_drawn_ns) {
+ os << *p.report_fully_drawn_ns;
+ } else {
+ os << "(nullopt)";
+ }
+ os << "}";
+ return os;
+}
+
+} // namespace iorap::db
+
+#endif // IORAP_SRC_DB_MODELS_H_