Files
eUi/include/sql/qSqlBase.h

695 lines
24 KiB
C++

#include <qfile.h>
#include <string>
#include <unordered_map>
#include <mutex>
#include <qdatetime.h>
#include <memory>
#include <qtextstream.h>
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlError>
#include <QtSql/QSqlRecord>
#include <QVariant>
#include <QDebug>
#include <QString>
#include <QList>
#include <QMap>
#include <qcoreapplication.h>
#include <qdir.h>
#include <qtimer.h>
#include <chrono>
#include <qobject.h>
#include <generalJsonParsing.hpp>
#include <iostream>
#include <fstream>
#include <qelapsedtimer.h>
#include <QQueue>
#include <quuid.h>
#include <global.h>
#include <qthread.h>
#include <qmutex.h>
class SQLiteDB {
public:
QSqlDatabase db;
// 新增:全局互斥锁,保护所有数据库操作
mutable std::mutex db_mutex;
SQLiteDB(const QString& dbPath)
: dbPath(dbPath) {
connect();
}
~SQLiteDB() { disconnect(); };
// 所有公有方法加锁保护
bool execute(const QString& queryStr, const QVariantList& args = {}) {
std::lock_guard<std::mutex> lock(db_mutex);
QSqlQuery query(db);
query.prepare(queryStr);
for (int i = 0; i < args.size(); ++i) {
query.bindValue(i, args[i]);
}
if (!query.exec()) {
qCritical() << "SQL error:" << query.lastError().text();
qCritical() << "Query:" << queryStr;
return false;
}
return true;
}
QList<QMap<QString, QVariant>> query(const QString& queryStr, const QVariantList& args = {}) {
std::lock_guard<std::mutex> lock(db_mutex);
QList<QMap<QString, QVariant>> results;
QSqlQuery query(db);
query.prepare(queryStr);
for (int i = 0; i < args.size(); ++i) {
query.bindValue(i, args[i]);
}
if (!query.exec()) {
qCritical() << "SQL error:" << query.lastError().text();
qCritical() << "Query:" << queryStr;
return results;
}
while (query.next()) {
QMap<QString, QVariant> row;
for (int i = 0; i < query.record().count(); ++i) {
row.insert(query.record().fieldName(i), query.value(i));
}
results.append(row);
}
return results;
}
bool createTable(const QString& tableName, const QList<std::pair<QString, QString>>& columns) {
std::lock_guard<std::mutex> lock(db_mutex);
if (columns.empty()) {
qDebug() << "Create table failed: columns are empty.";
return false;
}
QStringList columnDefinitions;
for (const auto& pair : columns) {
columnDefinitions << QString("%1 %2").arg(pair.first).arg(pair.second);
}
QString sql = QString("CREATE TABLE IF NOT EXISTS \"%1\" (%2)")
.arg(tableName)
.arg(columnDefinitions.join(", "));
QSqlQuery query(db);
bool res = query.exec(sql);
return res;
}
std::vector<QString> getTables() {
std::lock_guard<std::mutex> lock(db_mutex);
std::vector<QString> tables;
QString sql = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';";
QSqlQuery query(db);
if (!query.exec(sql)) {
qDebug() << "getTables failed:" << query.lastError().text();
return tables;
}
while (query.next()) {
tables.push_back(query.value(0).toString());
}
return tables;
}
bool dropTable(const QString& tableName) {
std::lock_guard<std::mutex> lock(db_mutex);
QString sql = QString("DROP TABLE IF EXISTS %1").arg(tableName);
return execute(sql);
}
bool addColumn(const QString& tableName, const QString& columnName, const QString& columnType) {
std::lock_guard<std::mutex> lock(db_mutex);
if (columnName.isEmpty() || columnType.isEmpty()) {
qDebug() << "Add column failed: column name or type is empty.";
return false;
}
QString sql = QString("ALTER TABLE \"%1\" ADD COLUMN \"%2\" %3").arg(tableName, columnName, columnType);
QSqlQuery query(db);
query.prepare(sql);
if (!query.exec()) {
qDebug() << "Add column failed:" << query.lastError().text();
return false;
}
qDebug() << "Column" << columnName << "added to table" << tableName;
return true;
}
bool tableExists(const QString& tableName) {
std::lock_guard<std::mutex> lock(db_mutex);
QString sql = QString("SELECT name FROM sqlite_master WHERE type='table' AND name='%1';").arg(tableName);
QList<QMap<QString, QVariant>> result = query(sql);
return !result.isEmpty();
}
bool _insert(const QString& tableName,
const std::map<QString, QVariant>& data) {
std::lock_guard<std::mutex> lock(db_mutex);
if (data.empty()) {
qDebug() << "Insert failed: data is empty.";
return false;
}
if (tableName.isEmpty()) {
qDebug() << "Insert failed: tableName is empty.";
return false;
}
QStringList columns, values;
for (const auto& pair : data) {
columns << pair.first;
values << "'" + pair.second.toString().replace("'", "''") + "'";
}
QString sql = QString("INSERT INTO \"%1\" (%2) VALUES (%3)")
.arg(tableName)
.arg(columns.join(", "))
.arg(values.join(", "));
qDebug() << "Generated SQL: " << sql;
QSqlQuery query(db);
if (!db.isOpen()) {
qDebug() << "Database is not open!";
return false;
}
if (!query.exec(sql)) {
qDebug() << "Insert failed:" << query.lastError().text();
return false;
}
return true;
}
bool _update(const QString& tableName,
const std::map<QString, QVariant>& data,
const std::map<QString, QVariant>& conditions) {
std::lock_guard<std::mutex> lock(db_mutex);
if (data.empty() || conditions.empty()) {
qDebug() << "Update failed: data or conditions are empty.";
return false;
}
QStringList setClauses;
for (const auto& pair : data) {
QString value = pair.second.toString().replace("'", "''");
setClauses << QString("%1 = '%2'").arg(pair.first).arg(value);
}
QStringList whereClauses;
for (const auto& pair : conditions) {
QString conditionValue = pair.second.toString().replace("'", "''");
whereClauses << QString("%1 = '%2'").arg(pair.first).arg(conditionValue);
}
QString sql = QString("UPDATE \"%1\" SET %2 WHERE %3")
.arg(tableName)
.arg(setClauses.join(", "))
.arg(whereClauses.join(" AND "));
qDebug() << "Generated SQL: " << sql;
QSqlQuery query(db);
if (!db.isOpen()) {
qDebug() << "Database is not open!";
return false;
}
if (!query.exec(sql)) {
qDebug() << "Update failed:" << query.lastError().text();
return false;
}
return true;
}
bool _delete(const QString& tableName,
const std::map<QString, QVariant>& conditions) {
std::lock_guard<std::mutex> lock(db_mutex);
if (conditions.empty()) {
qDebug() << "Delete failed: conditions are empty.";
return false;
}
QStringList whereClauses;
for (const auto& pair : conditions) {
whereClauses << QString("%1 = :%2").arg(pair.first).arg(pair.first + "_condition");
}
QString sql = QString("DELETE FROM \"%1\" WHERE %2")
.arg(tableName)
.arg(whereClauses.join(" AND "));
QSqlQuery query(db);
query.prepare(sql);
for (const auto& pair : conditions) {
query.bindValue(":" + pair.first + "_condition", QVariant::fromValue(pair.second));
}
if (!query.exec()) {
qDebug() << "Delete failed:" << query.lastError().text();
return false;
}
return true;
}
std::vector<std::map<QString, QVariant>> _search(const QString& tableName,
const QStringList& columns,
const std::map<QString, QVariant>& conditions = {}) {
std::lock_guard<std::mutex> lock(db_mutex);
QStringList selectColumns = columns.isEmpty() ? QStringList("*") : columns;
QString sql = QString("SELECT %1 FROM \"%2\"").arg(selectColumns.join(", "), tableName);
if (!conditions.empty()) {
QStringList whereClauses;
for (const auto& pair : conditions) {
QString value = pair.second.toString().replace("'", "''");
whereClauses << QString("%1 = '%2'").arg(pair.first).arg(value);
}
sql += " WHERE " + whereClauses.join(" AND ");
}
qDebug() << "Generated SQL: " << sql;
QSqlQuery query(db);
if (!db.isOpen()) {
qDebug() << "Database is not open!";
return {};
}
if (!query.exec(sql)) {
qDebug() << "Search failed:" << query.lastError().text();
return {};
}
std::vector<std::map<QString, QVariant>> results;
while (query.next()) {
std::map<QString, QVariant> row;
for (int i = 0; i < query.record().count(); ++i) {
row[query.record().fieldName(i)] = query.value(i);
}
results.push_back(row);
}
return results;
}
QList<QMap<QString, QVariant>> queryTabelData(const QString& tableName) {
std::lock_guard<std::mutex> lock(db_mutex);
QString sql = QString("SELECT * FROM \"%1\"").arg(tableName);
return query(sql);
}
bool exportTableToCSV(const QString& tableName, const QString& filePath, std::mutex& _mutex) {
std::lock_guard<std::mutex> lock(db_mutex);
using namespace std::chrono;
auto start = high_resolution_clock::now();
QString csvFileName = filePath;
QString sql = QString("SELECT * FROM \"%1\"").arg(tableName);
QSqlQuery query(db);
if (!query.exec(sql)) {
qCritical() << "Query execution failed: " << query.lastError().text();
return false;
}
if (!query.next()) {
qDebug() << "No data found in table" << tableName;
return false;
}
QFile file(csvFileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qCritical() << "Failed to open file" << csvFileName;
return false;
}
QTextStream out(&file);
QSqlRecord record = query.record();
QStringList columns;
for (int i = 0; i < record.count(); ++i) {
columns << record.fieldName(i);
}
out << columns.join(",") << "\n";
do {
QStringList values;
for (const auto& column : columns) {
values << query.value(column).toString();
}
out << values.join(",") << "\n";
} while (query.next());
file.close();
auto end = high_resolution_clock::now();
auto duration = duration_cast<milliseconds>(end - start);
qDebug() << csvFileName << "EXPORT CSV COST: " << duration.count() << "ms";
return true;
}
bool setWALMode(bool enableWAL = true, int busyTimeoutMs = 3000) {
std::lock_guard<std::mutex> lock(db_mutex);
if (!db.isOpen()) {
qWarning() << "Database is not open, cannot set WAL mode.";
return false;
}
QSqlQuery pragma(db);
QString mode = enableWAL ? "WAL" : "DELETE";
if (!pragma.exec(QString("PRAGMA journal_mode=%1;").arg(mode))) {
qWarning() << "Failed to set journal_mode:" << pragma.lastError().text();
return false;
}
qDebug() << "SQLite journal_mode set to" << mode;
if (enableWAL) {
pragma.exec("PRAGMA synchronous=NORMAL;");
}
pragma.exec(QString("PRAGMA busy_timeout=%1;").arg(busyTimeoutMs));
return true;
}
private:
bool connect() {
m_connectionName = QUuid::createUuid().toString();
if (!QFile::exists(dbPath)) {
QFileInfo fi(dbPath);
QDir().mkpath(fi.absolutePath());
qDebug() << "Database file does not exist. Creating a new database:" << dbPath;
}
db = QSqlDatabase::addDatabase("QSQLITE", m_connectionName);
db.setDatabaseName(dbPath);
if (!db.open()) {
qCritical() << "Failed to open or create database:" << db.lastError().text();
return false;
}
qDebug() << "Connected to SQLite database:" << dbPath;
{
QSqlQuery pragma(db);
if (!pragma.exec("PRAGMA journal_mode=WAL;")) {
qWarning() << "Failed to enable WAL mode:" << pragma.lastError().text();
}
else {
qDebug() << "SQLite WAL mode enabled.";
}
pragma.exec("PRAGMA synchronous=NORMAL;");
pragma.exec("PRAGMA busy_timeout=3000;");
}
createTable("setting",
{
{"id","INTEGER PRIMARY KEY AUTOINCREMENT"} ,
});
return true;
}
void disconnect() {
QSqlDatabase::removeDatabase(m_connectionName);
if (db.isOpen()) {
db.close();
qDebug() << "Database closed.";
}
}
QString m_connectionName;
QString dbPath;
};
class CsvWriter : public QThread{
public:
CsvWriter(SQLiteDB* dbIn):db(dbIn){
try{
m_json = readJsonFromPath("./sqldrivers/sqliteCfg.json");
for (auto& data : m_json["数据列表"]) {
db_InitColumns.append({ QString::fromStdString(data),"TEXT" });
}
for (auto& data : m_json["链接到UI表格数据列表"].items()) {
csvAndChartLink[QString::fromStdString(data.key())] = QString::fromStdString(data.value());
}
if (m_json["新增数据列表"].size() > 0) {
for (auto& data : m_json["新增数据列表"]) {
db_newCol.append({ QString::fromStdString(data),"TEXT" });
}
m_json["新增数据列表"].clear();
saveJsonFromPath("./sqldrivers/sqliteCfg.json", m_json);
}
for (auto& data : m_json["判断NG依据数据列表"]) {
isNgDepends.push_back(QString::fromStdString(data));
}
}
catch (const std::exception&){
std::cerr << "readJsonFromPath error" << std::endl;
}
init();
}
~CsvWriter(){
//stopAutoExport();
}
std::map<QString, QString>csvAndChartLink;
SQLiteDB* db;
void addData(QString snCode, std::map<QString, QVariant> data) {
if (snCode.isEmpty()) {
qWarning() << "addtData: snCode is empty.";
return;
}
if (data.empty()) {
qWarning() << "addtData: data is empty.";
return;
}
QMutexLocker locker(&queueLock);
queue.enqueue(std::make_pair(snCode, data));
this->start();
}
bool isNewData(const QString& snCode) {
std::lock_guard<std::mutex> locker(db->db_mutex);
auto res = db->_search(getCurrentProcessCsvName(), {}, { {"条码",snCode} });
return res.empty();
}
bool isNgData(const QString& snCode) {
std::lock_guard<std::mutex> locker(db->db_mutex);
auto res = db->_search(getCurrentProcessCsvName(), {}, { {"条码",snCode} });
if (res.empty() == true)
return false;
for (auto& data : isNgDepends) {
if (res[0][data].toString() == "NG")
return true;
}
return false;
}
void run() override{
while (queue.size() > 0) {
queueLock.lock();
auto val = queue.dequeue();
queueLock.unlock();
try {
auto snCode = val.first;
auto data = val.second;
QString currentProcessCsvName = getCurrentProcessCsvName();
if (currentProcessCsvName != currentProcessCsvNameCache) {
std::lock_guard<std::mutex> locker(db->db_mutex);
if (!db->createTable(currentProcessCsvName, db_InitColumns)) {
qWarning() << "addtData: Failed to create table:" << currentProcessCsvName;
return;
}
currentProcessCsvNameCache = currentProcessCsvName;
}
QList<QString> tableColumns;
{
std::lock_guard<std::mutex> locker(db->db_mutex);
QSqlQuery colQuery(db->db);
QString colSql = QString("PRAGMA table_info(\"%1\")").arg(currentProcessCsvName);
if (colQuery.exec(colSql)) {
while (colQuery.next()) {
tableColumns.append(colQuery.value(1).toString());
}
}
}
for (const auto& kv : data) {
std::lock_guard<std::mutex> locker(db->db_mutex);
if (!tableColumns.contains(kv.first)) {
if (!db->addColumn(currentProcessCsvName, kv.first, "TEXT")) {
qWarning() << "addtData: Failed to add column:" << kv.first << "to table:" << currentProcessCsvName;
}
}
}
std::vector<std::map<QString, QVariant>> res;
{
std::lock_guard<std::mutex> locker(db->db_mutex);
res = db->_search(currentProcessCsvName, {}, { {"条码",snCode} });
}
QString nowTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
bool opResult = false;
if (res.empty()) {
std::vector<std::map<QString, QVariant>> lastRes;
{
std::lock_guard<std::mutex> locker(db->db_mutex);
lastRes = db->_search(getLastProcessCsvName(), {}, { {"条码",snCode} });
}
if (lastRes.empty()) {
std::map<QString, QVariant> insertData = data;
insertData["条码"] = snCode;
insertData["创建时间"] = nowTime;
insertData["修改时间"] = nowTime;
std::lock_guard<std::mutex> locker(db->db_mutex);
opResult = db->_insert(currentProcessCsvName, insertData);
if (!opResult) {
qWarning() << "addtData: Insert failed for table:" << currentProcessCsvName << "snCode:" << snCode;
}
}
else {
std::map<QString, QVariant> updateData = data;
updateData["修改时间"] = nowTime;
std::lock_guard<std::mutex> locker(db->db_mutex);
opResult = db->_update(getLastProcessCsvName(), updateData, { {"条码",snCode} });
if (!opResult) {
qWarning() << "addtData: Update failed for table:" << getLastProcessCsvName() << "snCode:" << snCode;
}
}
}
else {
std::map<QString, QVariant> updateData = data;
updateData["修改时间"] = nowTime;
std::lock_guard<std::mutex> locker(db->db_mutex);
opResult = db->_update(getCurrentProcessCsvName(), updateData, { {"条码",snCode} });
if (!opResult) {
qWarning() << "addtData: Update failed for table:" << getCurrentProcessCsvName() << "snCode:" << snCode;
}
}
}
catch (const std::exception& ex) {
qCritical() << "addtData: Exception caught:" << ex.what();
}
catch (...) {
qCritical() << "addtData: Unknown exception caught.";
}
}
}
private:
QMutex queueLock;
QQueue<std::pair<QString,std::map<QString, QVariant>>> queue;
std::vector<QString> isNgDepends;
QQueue<std::pair< QString, std::map<QString, QVariant>> > addDataCacheQueue;
std::mutex _mutex;
int m_intervalMs = 10000;
QElapsedTimer timer;
QString currentProcessCsvNameCache;
nlohmann::json m_json;
QList<std::pair<QString, QString>> db_newCol = {};
QList<std::pair<QString, QString>> db_InitColumns = {
{"条码", "TEXT NOT NULL PRIMARY KEY"},
{"创建时间", "TEXT"},
{"修改时间", "TEXT"},
{"CT", "TEXT"},
};
void init(){
std::lock_guard<std::mutex> locker(_mutex);
if (!db->createTable(getLastProcessCsvName(), db_InitColumns)) {
qDebug() << "Failed to create table " << getLastProcessCsvName();
}
currentProcessCsvNameCache = getCurrentProcessCsvName();
if (!db->createTable(currentProcessCsvNameCache, db_InitColumns)) {
qDebug() << "Failed to create table " << currentProcessCsvNameCache;
}
for (auto& data : db_newCol) {
db->addColumn(getLastProcessCsvName(), data.first, data.second);
db->addColumn(getCurrentProcessCsvName(), data.first, data.second);
}
timer.start();
}
QString getCurrentProcessCsvName() {
QDateTime currentDateTime = QDateTime::currentDateTime();
int hour = currentDateTime.time().hour();
int minute = currentDateTime.time().minute();
bool isDayShift = (hour > 8 || (hour == 8 && minute >= 30)) && (hour < 20 || (hour == 20 && minute <= 30));
bool isDayBefore = hour <= 8 || (hour == 8 && minute <= 29);
QString dateStr;
if (isDayBefore == true)
dateStr = currentDateTime.addDays(-1).toString("yyyy-MM-dd");
else
dateStr = currentDateTime.toString("yyyy-MM-dd");
if (isDayShift) {
return (dateStr + "白班");
}
else {
return (dateStr + "夜班");
}
}
QString getLastProcessCsvName() {
QDateTime currentDateTime = QDateTime::currentDateTime();
int hour = currentDateTime.time().hour();
int minute = currentDateTime.time().minute();
bool isDayShift = (hour == 8 && minute >= 30) && (hour == 20 && minute <= 30);
bool isDayBefore = hour == 20 && minute <= 30;
QDateTime lastShiftDateTime;
if (isDayBefore) {
lastShiftDateTime = currentDateTime.addDays(-1);
}
else
lastShiftDateTime = currentDateTime;
QString dateStr = lastShiftDateTime.toString("yyyy-MM-dd");
if (isDayShift) {
return (dateStr + "夜班");
}
else {
return (dateStr + "白班");
}
}
void exportCsv(){
QString name = getLastProcessCsvName();
db->exportTableToCSV(name, "./csv/"+ name +".csv", _mutex);
name = getCurrentProcessCsvName();
db->exportTableToCSV(name, "./csv/" + name + ".csv", _mutex);
QString currentProcessCsvName = getCurrentProcessCsvName();
if (currentProcessCsvName != currentProcessCsvNameCache)
if (!db->createTable(currentProcessCsvName, db_InitColumns)) {
return;
}
else
currentProcessCsvNameCache = currentProcessCsvName;
}
};
#pragma once