#include "FaceDatabase.h" namespace ANSCENTER { FaceDatabase::FaceDatabase() { m_db = nullptr; m_embedding_dim = 0; } bool FaceDatabase::Initialize(std::string path, int embedding_dim, std::string dataFolder) { std::lock_guard lock(_mutex); try { sqlite3_stmt* stmt; _dataFolder = dataFolder; m_embedding_dim = embedding_dim; std::string sql; int rc; int isNotEmpty = 0; //this->_logger.LogDebug("FaceDatabase::Initialize. Database path: ", path, __FILE__, __LINE__); std::string faceDatabaseFolder = GetParentFolder(path); if (!FolderExist(faceDatabaseFolder)) fs::create_directory(faceDatabaseFolder); std::string imageDataFolder = CreateFilePath(faceDatabaseFolder, "data"); if (!FolderExist(imageDataFolder)) fs::create_directory(imageDataFolder); rc = sqlite3_open(path.c_str(), &m_db); if (rc) { _logger.LogError("FaceDatabase::Initialize", std::string("Can't open database: ") + sqlite3_errmsg(m_db), __FILE__, __LINE__); } else { _logger.LogDebug("FaceDatabase::Initialize", "Opened database successfully", __FILE__, __LINE__); } // Fix #2: Enable WAL mode for concurrent read/write from separate processes sqlite3_exec(m_db, "PRAGMA journal_mode=WAL;", nullptr, nullptr, nullptr); // Fix #2: 5-second busy timeout for multi-process contention sqlite3_exec(m_db, "PRAGMA busy_timeout=5000;", nullptr, nullptr, nullptr); // Fix #2: Enable foreign key enforcement sqlite3_exec(m_db, "PRAGMA foreign_keys=ON;", nullptr, nullptr, nullptr); // Prepare sql = "SELECT name FROM sqlite_master;"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::Initialize. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); } // Step while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { isNotEmpty = 1; break; } sqlite3_finalize(stmt); if (isNotEmpty) { //this->_logger.LogDebug("FaceDatabase::Initialize.", "Database is not empty", __FILE__, __LINE__); } else { //this->_logger.LogDebug("FaceDatabase::Initialize.", "Empty Database, initiating...", __FILE__, __LINE__); // Prepare sql = "CREATE TABLE IF NOT EXISTS USER ( \ ID INTEGER PRIMARY KEY AUTOINCREMENT, \ CODE TEXT, \ NAME TEXT, \ UNIQUE(ID, CODE)\ )"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::Initialize. SQL step error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); } // Step rc = sqlite3_step(stmt); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { // this->_logger.LogError("FaceDatabase::Initialize. SQL step error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); } else { // this->_logger.LogDebug("FaceDatabase::Initialize.", "Table `USER` created successfully", __FILE__, __LINE__); } sqlite3_finalize(stmt); // Prepare sql = "CREATE TABLE IF NOT EXISTS FACE ( \ ID INTEGER PRIMARY KEY AUTOINCREMENT, \ USER INTEGER, \ IMG_PATH TEXT, \ EMBEDDING BLOB, \ UNIQUE(ID, USER), \ FOREIGN KEY(USER) REFERENCES USER(ID) \ )"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::Initialize. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); } // Step rc = sqlite3_step(stmt); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { // this->_logger.LogError("FaceDatabase::Initialize. SQL step error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); } else { // this->_logger.LogDebug("FaceDatabase::Initialize", "Table `FACE` created successfully.", __FILE__, __LINE__); } sqlite3_finalize(stmt); } return true; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::Initialize", e.what(), __FILE__, __LINE__); //sqlite3_finalize(stmt); return false; } } int FaceDatabase::InsertUser(std::string userId, std::string userName) { std::lock_guard lock(_mutex); std::string sql; sqlite3_stmt* stmt; int rc; int resultCode = 0; // Prepare sql = "INSERT INTO USER (CODE, NAME) VALUES (?, ?);"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); try { if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::InsertUser. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); return -2; } // Binding rc = sqlite3_bind_text(stmt, 1, userId.c_str(), int(userId.length()), SQLITE_STATIC); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::InsertUser. SQL bind error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -3; } rc = sqlite3_bind_text(stmt, 2, userName.c_str(), int(userName.length()), SQLITE_STATIC); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::InsertUser. SQL bind error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -3; } // Step rc = sqlite3_step(stmt); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { //this->_logger.LogError("FaceDatabase::InsertUser. SQL step error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -4; } if (rc == SQLITE_DONE) { // this->_logger.LogDebug("FaceDatabase::InsertUser:", "Record successfully created", __FILE__, __LINE__); } sqlite3_finalize(stmt); int id = (int)sqlite3_last_insert_rowid(m_db); resultCode = id; return resultCode; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::InsertUser", e.what(), __FILE__, __LINE__); sqlite3_finalize(stmt); return -1; } } int FaceDatabase::UpdateUser(int userId, std::string newUserCode, std::string newUserName) { std::lock_guard lock(_mutex); std::string sql; sqlite3_stmt* stmt; int resultCode = 0; int rc; // Prepare // UPDATE USER SET CODE ="0003", NAME= "Biden" WHERE ID =1 sql = "UPDATE USER SET CODE=?, NAME=? WHERE ID=?;"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); try { if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::InsertUser. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); return -1; } // Binding rc = sqlite3_bind_text(stmt, 1, newUserCode.c_str(), int(newUserCode.length()), SQLITE_STATIC); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::InsertUser. SQL bind error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -2; } rc = sqlite3_bind_text(stmt, 2, newUserName.c_str(), int(newUserName.length()), SQLITE_STATIC); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::InsertUser. SQL bind error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -2; } rc = sqlite3_bind_int(stmt, 3, userId); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::InsertUser. SQL bind error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -2; } // Step rc = sqlite3_step(stmt); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { //this->_logger.LogError("FaceDatabase::InsertUser. SQL step error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -3; } if (rc == SQLITE_DONE) { // this->_logger.LogDebug("FaceDatabase::InsertUser:", "Record successfully created", __FILE__, __LINE__); } sqlite3_finalize(stmt); resultCode = 1; return resultCode; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::InsertUser", e.what(), __FILE__, __LINE__); sqlite3_finalize(stmt); return 0; } } int FaceDatabase::InsertFace(int userId, std::string imgPath, float embedding[]) { std::lock_guard lock(_mutex); if (!FileExist(imgPath)) return 0; std::string sql; sqlite3_stmt* stmt; int rc; int resultCode = 0; // Prepare sql = "INSERT INTO FACE (USER, IMG_PATH, EMBEDDING) VALUES (?, ?, ?);"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); try { if (rc != SQLITE_OK) { //std::cout << "SQL error: " << sqlite3_errmsg(m_db); return -2; } // Binding rc = sqlite3_bind_int(stmt, 1, userId); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::InsertFace. SQL bind error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -3; } rc = sqlite3_bind_text(stmt, 2, imgPath.c_str(), int(imgPath.length()), SQLITE_STATIC); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::InsertFace. SQL bind error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -3; } rc = sqlite3_bind_blob(stmt, 3, embedding, sizeof(float) * m_embedding_dim, SQLITE_STATIC); if (rc != SQLITE_OK) { //::cout << "SQL bind error: " << sqlite3_errmsg(m_db); resultCode = -3; } // Step rc = sqlite3_step(stmt); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { //this->_logger.LogError("FaceDatabase::InsertFace. SQL step error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -4; } if (rc == SQLITE_DONE) { //this->_logger.LogDebug("FaceDatabase::InsertFace.", "Embedding for user created successfully", __FILE__, __LINE__); } sqlite3_finalize(stmt); int id = (int)sqlite3_last_insert_rowid(m_db); resultCode = id; return resultCode; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::InsertFace", e.what(), __FILE__, __LINE__); sqlite3_finalize(stmt); return -1; } } int FaceDatabase::DeleteFace(int id) { try { // Fix #4: Single lock scope — GetFaceById + DELETE in one atomic section std::lock_guard lock(_mutex); std::string sql; sqlite3_stmt* stmt; int rc; int userId; int resultCode = 0; std::string faceImagePath; // Look up face image path (now inside lock — no TOCTOU race) { sqlite3_stmt* lookupStmt; rc = sqlite3_prepare_v2(m_db, "SELECT USER, IMG_PATH FROM FACE WHERE ID=?;", -1, &lookupStmt, NULL); if (rc == SQLITE_OK) { sqlite3_bind_int(lookupStmt, 1, id); if (sqlite3_step(lookupStmt) == SQLITE_ROW) { userId = sqlite3_column_int(lookupStmt, 0); const char* pathText = reinterpret_cast(sqlite3_column_text(lookupStmt, 1)); faceImagePath = pathText ? std::string(pathText) : ""; } } sqlite3_finalize(lookupStmt); } // Delete image file if (!faceImagePath.empty() && FileExist(faceImagePath)) { if (!fs::remove(faceImagePath)) { _logger.LogError("FaceDatabase::DeleteFace", "Failed to delete face image: " + faceImagePath, __FILE__, __LINE__); } } //_mutex.lock(); // Prepare sql = "DELETE FROM FACE WHERE ID=?;"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::DeleteFace. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -1; } // Binding rc = sqlite3_bind_int(stmt, 1, id); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::DeleteFace. SQL bind error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -2; } // Step rc = sqlite3_step(stmt); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { //this->_logger.LogError("FaceDatabase::DeleteFace. SQL step error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -3; } if (rc == SQLITE_DONE) { // this->_logger.LogDebug("FaceDatabase::DeleteFace.", "Embedding for user deleted successfully", __FILE__, __LINE__); } sqlite3_finalize(stmt); resultCode = 1; //_mutex.unlock(); return resultCode; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::DeleteFace", e.what(), __FILE__, __LINE__); //_mutex.unlock(); return 0; } } int FaceDatabase::DeleteUser(int userId) { try { std::string sql; sqlite3_stmt* stmt; int resultCode = 0; int rc; std::vector faceIds; faceIds.clear(); GetFaceIdsByUser(userId, faceIds); int numOfFaces = (int)faceIds.size(); //this->_logger.LogDebug("FaceDatabase::DeleteUser. Number of Face for this user", std::to_string(numOfFaces), __FILE__, __LINE__); if (numOfFaces > 0) { for (int i = 0; i < numOfFaces; i++) { if (!DeleteFace(faceIds[i])) { //this->_logger.LogError("FaceDatabase::DeleteUser. Failed to delete face id", std::to_string(numOfFaces), __FILE__, __LINE__); } } } std::string userImageFolderName = "User" + std::to_string(userId); std::string userImageFolder = CreateFilePath(_dataFolder, userImageFolderName); if (FolderExist(userImageFolder)) { if (!DeleteFolder(userImageFolder)) { //this->_logger.LogError("FaceDatabase::DeleteUser. Failed to delete user data folder", userImageFolder, __FILE__, __LINE__); } } // Delete person std::lock_guard lock(_mutex); //_mutex.lock(); // Prepare sql = "DELETE FROM USER WHERE ID=?;"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::DeleteUser. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -1; } // Binding rc = sqlite3_bind_int(stmt, 1, userId); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::DeleteUser. SQL bind error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -2; } // Step rc = sqlite3_step(stmt); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { //this->_logger.LogError("FaceDatabase::DeleteUser. SQL step error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -3; } if (rc == SQLITE_DONE) { //this->_logger.LogDebug("FaceDatabase::DeleteUser.", "Deleted person successfully", __FILE__, __LINE__); } sqlite3_finalize(stmt); resultCode = 1; //_mutex.unlock(); return resultCode; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::DeleteUser", e.what(), __FILE__, __LINE__); return 0; } } int FaceDatabase::GetUserId(std::string code) { std::lock_guard lock(_mutex); std::string sql; sqlite3_stmt* stmt; int rc; // Prepare sql = "SELECT ID FROM USER WHERE CODE =?;"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); try { int resultCode = 0; int userId = -1; if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::GetUser. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -1; } // Binding rc = sqlite3_bind_text(stmt, 1, code.c_str(), int(code.length()), SQLITE_STATIC); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::GetUser. SQL bind error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -2; } // Step rc = sqlite3_step(stmt); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { //this->_logger.LogError("FaceDatabase::GetUser. SQL step error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -3; } if (rc == SQLITE_ROW) { //this->_logger.LogDebug("FaceDatabase::GetUser.", "Get User information successfully", __FILE__, __LINE__); userId = sqlite3_column_int(stmt, 0); } sqlite3_finalize(stmt); resultCode = userId; return resultCode; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::GetUserId", e.what(), __FILE__, __LINE__); sqlite3_finalize(stmt); return -1; } } int FaceDatabase::GetUser(int userId, std::string& code, std::string& userName) { std::lock_guard lock(_mutex); std::string sql; sqlite3_stmt* stmt; int resultCode = 0; userName = ""; code = ""; int rc; // Prepare sql = "SELECT * FROM USER WHERE ID =?;"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); try { if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::GetUser. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -1; } // Binding rc = sqlite3_bind_int(stmt, 1, userId); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::GetUser. SQL bind error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -2; } // Step rc = sqlite3_step(stmt); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { //this->_logger.LogError("FaceDatabase::GetUser. SQL step error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -3; } if (rc == SQLITE_ROW) { //this->_logger.LogDebug("FaceDatabase::GetUser.", "Get User information successfully", __FILE__, __LINE__); const char* codeText = reinterpret_cast(sqlite3_column_text(stmt, 1)); const char* nameText = reinterpret_cast(sqlite3_column_text(stmt, 2)); code = codeText ? std::string(codeText) : ""; userName = nameText ? std::string(nameText) : ""; } sqlite3_finalize(stmt); resultCode = 1; return resultCode; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::GetUserId", e.what(), __FILE__, __LINE__); sqlite3_finalize(stmt); return -1; } } int FaceDatabase::GetFaceIdsByUser(int userId, std::vector& faceIds) { std::lock_guard lock(_mutex); faceIds.clear(); std::string sql; sqlite3_stmt* stmt; int rc; sql = "SELECT ID FROM FACE WHERE USER=?;"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); try { int resultCode = 0; int faceId; if (rc != SQLITE_OK) { _logger.LogError("FaceDatabase::GetFaceIdsByUser", std::string("SQL error: ") + sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -1; } // Binding rc = sqlite3_bind_int(stmt, 1, userId); if (rc != SQLITE_OK) { _logger.LogError("FaceDatabase::GetFaceIdsByUser", std::string("SQL bind error: ") + sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -2; } while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { faceId = sqlite3_column_int(stmt, 0); faceIds.push_back(faceId); } if (rc != SQLITE_DONE) { _logger.LogError("FaceDatabase::GetFaceIdsByUser", std::string("SQL step error: ") + sqlite3_errmsg(m_db), __FILE__, __LINE__); } sqlite3_finalize(stmt); resultCode = 1; return resultCode; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::GetFaceIdsByUser", e.what(), __FILE__, __LINE__); sqlite3_finalize(stmt); return 0; } } int FaceDatabase::GetUsers(std::vector& ids, std::vector& codes, std::vector& userNames) { std::lock_guard lock(_mutex); std::string sql; sqlite3_stmt* stmt; int rc; int userId; std::string userCode; std::string userName; int resutCode = 0; ids.clear(); codes.clear(); userNames.clear(); sql = "SELECT * FROM USER;"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); try { if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::GetUsers. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resutCode = -1; } while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { userId = sqlite3_column_int(stmt, 0); const char* codeText = reinterpret_cast(sqlite3_column_text(stmt, 1)); const char* nameText = reinterpret_cast(sqlite3_column_text(stmt, 2)); userCode = codeText ? std::string(codeText) : ""; userName = nameText ? std::string(nameText) : ""; ids.push_back(userId); codes.push_back(userCode); userNames.push_back(userName); } if (rc != SQLITE_DONE) { //this->_logger.LogError("FaceDatabase::GetUsers. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); } sqlite3_finalize(stmt); resutCode = 1; return resutCode; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::GetUsers", e.what(), __FILE__, __LINE__); sqlite3_finalize(stmt); return 0; } } int FaceDatabase::GetFaceById(int faceId, int& userId, std::string& imagePath) { std::lock_guard lock(_mutex); std::string sql; sqlite3_stmt* stmt; int rc; int resultCode = 0; userId = -1; imagePath = ""; // SELECT *FROM FACE WHERE ID =1 sql = "SELECT * FROM FACE WHERE ID =?;"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); try { if (rc != SQLITE_OK) { _logger.LogError("FaceDatabase::GetFaceById", std::string("SQL error: ") + sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -1; } // Binding rc = sqlite3_bind_int(stmt, 1, faceId); if (rc != SQLITE_OK) { _logger.LogError("FaceDatabase::GetFaceById", std::string("SQL bind error: ") + sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -2; } while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { userId = sqlite3_column_int(stmt, 1); const char* pathText = reinterpret_cast(sqlite3_column_text(stmt, 2)); imagePath = pathText ? std::string(pathText) : ""; } if (rc != SQLITE_DONE) { //this->_logger.LogError("FaceDatabase::GetFaceIds. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); } sqlite3_finalize(stmt); resultCode = 1; return resultCode; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::GetFaceIds", e.what(), __FILE__, __LINE__); sqlite3_finalize(stmt); return 0; } } std::map FaceDatabase::GetUserDict() { std::lock_guard lock(_mutex); std::map userDict; userDict.clear(); std::string sql; sqlite3_stmt* stmt; int rc; std::string userId; std::string userCode; std::string userName; // get user id and name sql = "SELECT * FROM USER;"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); try { if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::GetUserDict. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); } while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { const char* idText = reinterpret_cast(sqlite3_column_text(stmt, 0)); const char* codeText = reinterpret_cast(sqlite3_column_text(stmt, 1)); const char* nameText = reinterpret_cast(sqlite3_column_text(stmt, 2)); userId = idText ? std::string(idText) : ""; userCode = codeText ? std::string(codeText) : ""; userName = nameText ? std::string(nameText) : ""; if (!userCode.empty()) { userDict[userId] = userName+"(" + userCode + ")"; } else { userDict[userId] = userName; } } if (rc != SQLITE_DONE) { //this->_logger.LogError("FaceDatabase::GetUserDict. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); } sqlite3_finalize(stmt); return userDict; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::GetUserDict", e.what(), __FILE__, __LINE__); sqlite3_finalize(stmt); return userDict; } } int FaceDatabase::GetNumEmbeddings() { std::lock_guard lock(_mutex); try { int numEmbeds = 0; std::string sql; sqlite3_stmt* stmt; int rc; int resultCode = 0; // Prepare sql = "SELECT COUNT(*) FROM FACE;"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::GetNumEmbeddings. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -1; } // Step rc = sqlite3_step(stmt); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { //this->_logger.LogError("FaceDatabase::GetNumEmbeddings. SQL step error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -2; } numEmbeds = sqlite3_column_int(stmt, 0); sqlite3_finalize(stmt); resultCode = numEmbeds; return resultCode; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::GetNumEmbeddings", e.what(), __FILE__, __LINE__); return 0; } } int FaceDatabase::GetEmbeddings(std::unique_ptr& recognizer) { try { std::string sql; sqlite3_stmt* stmt; int rc; int resultCode = 0; std::string userId; int numEmbeds = GetNumEmbeddings(); _logger.LogDebug("FaceDatabase::GetEmbeddings", "There are " + std::to_string(numEmbeds) + " embeddings in database", __FILE__, __LINE__); // get embedding //_mutex.lock(); std::lock_guard lock(_mutex); sql = "SELECT * FROM FACE;"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::GetEmbeddings. SQL step error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -1; } while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { const char* userIdText = reinterpret_cast(sqlite3_column_text(stmt, 1)); if (!userIdText) continue; userId = std::string(userIdText); float* embedding = (float*)sqlite3_column_blob(stmt, 3); if (!embedding) continue; recognizer->AddEmbedding(userId, embedding); } if (rc != SQLITE_DONE) { //this->_logger.LogError("FaceDatabase::GetEmbeddings. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); } sqlite3_finalize(stmt); resultCode = 0; //_mutex.unlock(); return resultCode; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::GetEmbeddings", e.what(), __FILE__, __LINE__); //_mutex.unlock(); return 0; } } int FaceDatabase::GetEmbeddings(ANSFRBase& recognizer) { try { std::string sql; sqlite3_stmt* stmt; int rc; int resultCode = 0; std::string userId; int numEmbeds = GetNumEmbeddings(); _logger.LogDebug("FaceDatabase::GetEmbeddings", "There are " + std::to_string(numEmbeds) + " embeddings in database", __FILE__, __LINE__); // get embedding //_mutex.lock(); std::lock_guard lock(_mutex); sql = "SELECT * FROM FACE;"; rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, NULL); if (rc != SQLITE_OK) { //this->_logger.LogError("FaceDatabase::GetEmbeddings. SQL step error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); resultCode = -1; } while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { const char* userIdText = reinterpret_cast(sqlite3_column_text(stmt, 1)); if (!userIdText) continue; userId = std::string(userIdText); float* embedding = (float*)sqlite3_column_blob(stmt, 3); if (!embedding) continue; recognizer.AddEmbedding(userId, embedding); } if (rc != SQLITE_DONE) { //this->_logger.LogError("FaceDatabase::GetEmbeddings. SQL error:", sqlite3_errmsg(m_db), __FILE__, __LINE__); } sqlite3_finalize(stmt); resultCode = 0; //_mutex.unlock(); return resultCode; } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::GetEmbeddings", e.what(), __FILE__, __LINE__); //_mutex.unlock(); return 0; } } int FaceDatabase::GetAllEmbeddingRecords(std::vector& records) { try { std::lock_guard lock(_mutex); records.clear(); int numEmbeds = 0; { // Count for pre-allocation (inline, mutex already held) sqlite3_stmt* countStmt; int rc = sqlite3_prepare_v2(m_db, "SELECT COUNT(*) FROM FACE;", -1, &countStmt, NULL); if (rc == SQLITE_OK && sqlite3_step(countStmt) == SQLITE_ROW) { numEmbeds = sqlite3_column_int(countStmt, 0); } sqlite3_finalize(countStmt); } records.reserve(numEmbeds); sqlite3_stmt* stmt; int rc = sqlite3_prepare_v2(m_db, "SELECT ID, USER, EMBEDDING FROM FACE;", -1, &stmt, NULL); if (rc != SQLITE_OK) { return -1; } while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { int faceId = sqlite3_column_int(stmt, 0); const char* userIdText = reinterpret_cast( sqlite3_column_text(stmt, 1)); if (!userIdText) continue; const float* embData = reinterpret_cast( sqlite3_column_blob(stmt, 2)); int embBytes = sqlite3_column_bytes(stmt, 2); int embSize = embBytes / static_cast(sizeof(float)); if (!embData || embSize <= 0) continue; FaceEmbeddingRecord record; record.faceId = faceId; record.userId = std::string(userIdText); record.embedding.assign(embData, embData + embSize); records.push_back(std::move(record)); } sqlite3_finalize(stmt); return 0; } catch (const std::exception& e) { return -1; } } int FaceDatabase::GetUsersWithFaceIds(std::vector& ids, std::vector& codes, std::vector& userNames, std::vector>& faceIdsByUser) { // Fix #12: Single JOIN query replaces N+1 GetUser + GetFaceIdsByUser calls std::lock_guard lock(_mutex); ids.clear(); codes.clear(); userNames.clear(); faceIdsByUser.clear(); try { sqlite3_stmt* stmt; int rc = sqlite3_prepare_v2(m_db, "SELECT U.ID, U.CODE, U.NAME, F.ID AS FACE_ID " "FROM USER U LEFT JOIN FACE F ON U.ID = F.USER " "ORDER BY U.ID;", -1, &stmt, NULL); if (rc != SQLITE_OK) { _logger.LogError("FaceDatabase::GetUsersWithFaceIds", std::string("SQL error: ") + sqlite3_errmsg(m_db), __FILE__, __LINE__); return -1; } int lastUserId = -1; while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { int userId = sqlite3_column_int(stmt, 0); if (userId != lastUserId) { // New user row const char* codeText = reinterpret_cast(sqlite3_column_text(stmt, 1)); const char* nameText = reinterpret_cast(sqlite3_column_text(stmt, 2)); ids.push_back(userId); codes.push_back(codeText ? std::string(codeText) : ""); userNames.push_back(nameText ? std::string(nameText) : ""); faceIdsByUser.emplace_back(); lastUserId = userId; } // Append face ID (may be NULL if user has no faces — LEFT JOIN) if (sqlite3_column_type(stmt, 3) != SQLITE_NULL) { faceIdsByUser.back().push_back(sqlite3_column_int(stmt, 3)); } } if (rc != SQLITE_DONE) { _logger.LogError("FaceDatabase::GetUsersWithFaceIds", std::string("SQL step error: ") + sqlite3_errmsg(m_db), __FILE__, __LINE__); } sqlite3_finalize(stmt); return 1; } catch (const std::exception& e) { _logger.LogError("FaceDatabase::GetUsersWithFaceIds", e.what(), __FILE__, __LINE__); return 0; } } FaceDatabase::~FaceDatabase() { try { Destroy(); } catch (std::exception& e) { _logger.LogError("FaceDatabase::~FaceDatabase", e.what(), __FILE__, __LINE__); } } bool FaceDatabase::Destroy() { std::lock_guard lock(_mutex); try { if (!m_db) return true; int rc = sqlite3_close(m_db); if (rc == SQLITE_OK) { //this->_logger.LogDebug("Database::Destroy", "Success!", __FILE__, __LINE__); m_db = nullptr; return true; } else { std::string errorCode = "Cannot close database connection.Error code:" + std::to_string(rc); //this->_logger.LogError("Database::Destroy", errorCode, __FILE__, __LINE__); return false; } } catch (std::exception& e) { //this->_logger.LogFatal("FaceDatabase::Destroy", e.what(), __FILE__, __LINE__); return false; } } }