|
|
|
@ -1,14 +1,149 @@
|
|
|
|
|
#pragma once |
|
|
|
|
#include "merrors.h" |
|
|
|
|
#include "GPL.h" |
|
|
|
|
#include <functional> |
|
|
|
|
#include <libpq-fe.h> |
|
|
|
|
#include <sqlite3.h> |
|
|
|
|
#include <time.h> |
|
|
|
|
#include <variant> |
|
|
|
|
|
|
|
|
|
using michlib::GPL; |
|
|
|
|
using michlib::int_cast; |
|
|
|
|
using michlib::MString; |
|
|
|
|
using michlib::pointer_cast; |
|
|
|
|
|
|
|
|
|
class SQLiteConnection |
|
|
|
|
{ |
|
|
|
|
public: |
|
|
|
|
using DBType = sqlite3*; |
|
|
|
|
using FuncType = std::function<void(DBType)>; |
|
|
|
|
|
|
|
|
|
private: |
|
|
|
|
static DBType db; |
|
|
|
|
static size_t count; |
|
|
|
|
static std::vector<FuncType> destructs; |
|
|
|
|
|
|
|
|
|
public: |
|
|
|
|
SQLiteConnection() |
|
|
|
|
{ |
|
|
|
|
count++; |
|
|
|
|
if(db == nullptr) |
|
|
|
|
{ |
|
|
|
|
MString oldprefix = GPL.UsePrefix("SQLITE"); |
|
|
|
|
MString name = GPL.ParameterSValue("db", ""); |
|
|
|
|
GPL.UsePrefix(oldprefix); |
|
|
|
|
|
|
|
|
|
auto ret = sqlite3_open(name.Buf(), &db); |
|
|
|
|
if(ret != SQLITE_OK) |
|
|
|
|
{ |
|
|
|
|
sqlite3_close(db); |
|
|
|
|
db = nullptr; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
SQLiteConnection([[maybe_unused]] const SQLiteConnection& sq): SQLiteConnection() {} |
|
|
|
|
SQLiteConnection(SQLiteConnection&&) = default; |
|
|
|
|
|
|
|
|
|
SQLiteConnection& operator=([[maybe_unused]] const SQLiteConnection& sq) |
|
|
|
|
{ |
|
|
|
|
*this = {}; |
|
|
|
|
return *this; |
|
|
|
|
} |
|
|
|
|
SQLiteConnection& operator=(SQLiteConnection&&) = default; |
|
|
|
|
|
|
|
|
|
~SQLiteConnection() |
|
|
|
|
{ |
|
|
|
|
if(count == 0) michlib::errmessage("Destructor of SQLiteConnection called on count==0"); |
|
|
|
|
if(count > 1) |
|
|
|
|
count--; |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
count = 0; |
|
|
|
|
if(db != nullptr) |
|
|
|
|
{ |
|
|
|
|
for(const auto& f: destructs) f(db); |
|
|
|
|
sqlite3_close(db); |
|
|
|
|
} |
|
|
|
|
db = nullptr; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void AddDestructor(FuncType&& f) { destructs.emplace_back(std::move(f)); } |
|
|
|
|
|
|
|
|
|
operator DBType() const { return db; } |
|
|
|
|
|
|
|
|
|
static DBType GetDB() { return db; } |
|
|
|
|
|
|
|
|
|
explicit operator bool() const { return db != nullptr; } |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class PostgreSQLConnection |
|
|
|
|
{ |
|
|
|
|
public: |
|
|
|
|
using DBType = PGconn*; |
|
|
|
|
using FuncType = std::function<void(DBType)>; |
|
|
|
|
|
|
|
|
|
private: |
|
|
|
|
static DBType conn; |
|
|
|
|
static size_t count; |
|
|
|
|
static std::vector<FuncType> destructs; |
|
|
|
|
|
|
|
|
|
public: |
|
|
|
|
PostgreSQLConnection() |
|
|
|
|
{ |
|
|
|
|
count++; |
|
|
|
|
if(conn == nullptr) |
|
|
|
|
{ |
|
|
|
|
MString oldprefix = GPL.UsePrefix("POSTGRES"); |
|
|
|
|
MString name = GPL.ParameterSValue("connection", ""); |
|
|
|
|
GPL.UsePrefix(oldprefix); |
|
|
|
|
|
|
|
|
|
conn = PQconnectdb(name.Buf()); |
|
|
|
|
if(PQstatus(conn) != CONNECTION_OK) |
|
|
|
|
{ |
|
|
|
|
michlib::errmessage(PQerrorMessage(conn)); |
|
|
|
|
PQfinish(conn); |
|
|
|
|
conn = nullptr; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
PostgreSQLConnection([[maybe_unused]] const PostgreSQLConnection& pq): PostgreSQLConnection() {} |
|
|
|
|
PostgreSQLConnection(PostgreSQLConnection&&) = default; |
|
|
|
|
|
|
|
|
|
PostgreSQLConnection& operator=([[maybe_unused]] const PostgreSQLConnection& pq) |
|
|
|
|
{ |
|
|
|
|
*this = {}; |
|
|
|
|
return *this; |
|
|
|
|
} |
|
|
|
|
PostgreSQLConnection& operator=(PostgreSQLConnection&&) = default; |
|
|
|
|
|
|
|
|
|
~PostgreSQLConnection() |
|
|
|
|
{ |
|
|
|
|
if(count == 0) michlib::errmessage("Destructor of PostgreSQLConnection called on count==0"); |
|
|
|
|
if(count > 1) |
|
|
|
|
count--; |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
count = 0; |
|
|
|
|
if(conn != nullptr) |
|
|
|
|
{ |
|
|
|
|
for(const auto& f: destructs) f(conn); |
|
|
|
|
PQfinish(conn); |
|
|
|
|
} |
|
|
|
|
conn = nullptr; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void AddDestructor(FuncType&& f) { destructs.emplace_back(std::move(f)); } |
|
|
|
|
|
|
|
|
|
operator DBType() const { return conn; } |
|
|
|
|
|
|
|
|
|
static DBType GetDB() { return conn; } |
|
|
|
|
|
|
|
|
|
explicit operator bool() const { return conn != nullptr; } |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class GenericCache |
|
|
|
|
{ |
|
|
|
|
public: |
|
|
|
@ -27,44 +162,50 @@ class FakeCache: public GenericCache
|
|
|
|
|
|
|
|
|
|
class SQLiteCache: public GenericCache |
|
|
|
|
{ |
|
|
|
|
sqlite3* db = nullptr; |
|
|
|
|
static bool regdest; |
|
|
|
|
|
|
|
|
|
SQLiteConnection db; |
|
|
|
|
|
|
|
|
|
public: |
|
|
|
|
bool Init(const MString& name) |
|
|
|
|
bool Init() |
|
|
|
|
{ |
|
|
|
|
Close(); |
|
|
|
|
auto ret = sqlite3_open(name.Buf(), &db); |
|
|
|
|
if(ret != SQLITE_OK) |
|
|
|
|
{ |
|
|
|
|
Close(); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
// Create table
|
|
|
|
|
sqlite3_stmt* sqst; |
|
|
|
|
int i; |
|
|
|
|
|
|
|
|
|
i = sqlite3_prepare_v2(db, |
|
|
|
|
"CREATE TABLE IF NOT EXISTS `cache`('key' TEXT PRIMARY KEY ON CONFLICT REPLACE NOT NULL ON CONFLICT FAIL, 'value' BLOB NOT NULL ON CONFLICT FAIL, " |
|
|
|
|
"'exptime' INTEGER NOT NULL ON CONFLICT FAIL) WITHOUT ROWID, STRICT;", |
|
|
|
|
-1, &sqst, 0); |
|
|
|
|
i = sqlite3_step(sqst); |
|
|
|
|
if(i != SQLITE_DONE) |
|
|
|
|
if(!db) return false; |
|
|
|
|
|
|
|
|
|
if(!regdest) |
|
|
|
|
{ |
|
|
|
|
// Create table
|
|
|
|
|
sqlite3_stmt* sqst; |
|
|
|
|
int i; |
|
|
|
|
|
|
|
|
|
i = sqlite3_prepare_v2(db, |
|
|
|
|
"CREATE TABLE IF NOT EXISTS `cache`('key' TEXT PRIMARY KEY ON CONFLICT REPLACE NOT NULL ON CONFLICT FAIL, 'value' BLOB NOT NULL ON CONFLICT FAIL, " |
|
|
|
|
"'exptime' INTEGER NOT NULL ON CONFLICT FAIL) WITHOUT ROWID, STRICT;", |
|
|
|
|
-1, &sqst, 0); |
|
|
|
|
i = sqlite3_step(sqst); |
|
|
|
|
if(i != SQLITE_DONE) |
|
|
|
|
{ |
|
|
|
|
sqlite3_finalize(sqst); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
sqlite3_finalize(sqst); |
|
|
|
|
Close(); |
|
|
|
|
return false; |
|
|
|
|
sqlite3_busy_timeout(db, 1000); |
|
|
|
|
|
|
|
|
|
db.AddDestructor( |
|
|
|
|
[](SQLiteConnection::DBType db) |
|
|
|
|
{ |
|
|
|
|
sqlite3_stmt* sqst = nullptr; |
|
|
|
|
int i = SQLITE_OK; |
|
|
|
|
|
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_prepare_v2(db, "DELETE from `cache` WHERE exptime<?1;", -1, &sqst, 0); |
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_bind_int64(sqst, 1, time(nullptr)); |
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_step(sqst); |
|
|
|
|
sqlite3_finalize(sqst); |
|
|
|
|
}); |
|
|
|
|
regdest = true; |
|
|
|
|
} |
|
|
|
|
sqlite3_finalize(sqst); |
|
|
|
|
sqlite3_busy_timeout(db, 1000); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void Close() |
|
|
|
|
{ |
|
|
|
|
if(db != nullptr) sqlite3_close(db); |
|
|
|
|
db = nullptr; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
virtual bool Put(const MString& key, const MString& value, size_t ttl) const override |
|
|
|
|
{ |
|
|
|
|
if(!*this) return false; |
|
|
|
@ -107,26 +248,16 @@ class SQLiteCache: public GenericCache
|
|
|
|
|
return {"", false}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
virtual ~SQLiteCache() override |
|
|
|
|
{ |
|
|
|
|
if(!*this) return; |
|
|
|
|
|
|
|
|
|
sqlite3_stmt* sqst = nullptr; |
|
|
|
|
int i = SQLITE_OK; |
|
|
|
|
|
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_prepare_v2(db, "DELETE from `cache` WHERE exptime<?1;", -1, &sqst, 0); |
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_bind_int64(sqst, 1, time(nullptr)); |
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_step(sqst); |
|
|
|
|
sqlite3_finalize(sqst); |
|
|
|
|
sqlite3_close_v2(db); |
|
|
|
|
} |
|
|
|
|
virtual ~SQLiteCache() override = default; |
|
|
|
|
|
|
|
|
|
explicit operator bool() const { return db != nullptr; } |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class PostgreSQLCache: public GenericCache |
|
|
|
|
{ |
|
|
|
|
PGconn* conn = nullptr; |
|
|
|
|
static bool regdest; |
|
|
|
|
|
|
|
|
|
PostgreSQLConnection conn; |
|
|
|
|
|
|
|
|
|
bool CheckCon() const |
|
|
|
|
{ |
|
|
|
@ -148,41 +279,38 @@ class PostgreSQLCache: public GenericCache
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public: |
|
|
|
|
bool Init(const MString& name) |
|
|
|
|
bool Init() |
|
|
|
|
{ |
|
|
|
|
Close(); |
|
|
|
|
conn = PQconnectdb(name.Buf()); |
|
|
|
|
|
|
|
|
|
if(PQstatus(conn) != CONNECTION_OK) |
|
|
|
|
{ |
|
|
|
|
michlib::errmessage(PQerrorMessage(conn)); |
|
|
|
|
Close(); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
if(!conn) return false; |
|
|
|
|
|
|
|
|
|
// Create table
|
|
|
|
|
if(false) |
|
|
|
|
if(!regdest) |
|
|
|
|
{ |
|
|
|
|
auto* res = PQexec(conn, "CREATE TABLE IF NOT EXISTS cache(key TEXT PRIMARY KEY NOT NULL, value BYTEA, exptime TIMESTAMP(0) NOT NULL);"); |
|
|
|
|
if(PQresultStatus(res) != PGRES_COMMAND_OK) |
|
|
|
|
// Create table
|
|
|
|
|
if(false) |
|
|
|
|
{ |
|
|
|
|
michlib::errmessage(PQresStatus(PQresultStatus(res))); |
|
|
|
|
michlib::errmessage(PQerrorMessage(conn)); |
|
|
|
|
PQclear(res); |
|
|
|
|
Close(); |
|
|
|
|
auto* res = PQexec(conn, "CREATE TABLE IF NOT EXISTS cache(key TEXT PRIMARY KEY NOT NULL, value BYTEA, exptime TIMESTAMP(0) NOT NULL);"); |
|
|
|
|
if(PQresultStatus(res) != PGRES_COMMAND_OK) |
|
|
|
|
{ |
|
|
|
|
michlib::errmessage(PQresStatus(PQresultStatus(res))); |
|
|
|
|
michlib::errmessage(PQerrorMessage(conn)); |
|
|
|
|
PQclear(res); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
PQclear(res); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
PQclear(res); |
|
|
|
|
|
|
|
|
|
conn.AddDestructor( |
|
|
|
|
[](PostgreSQLConnection::DBType conn) |
|
|
|
|
{ |
|
|
|
|
auto* res = PQexec(conn, "DELETE FROM cache WHERE exptime<localtimestamp;"); |
|
|
|
|
PQclear(res); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
regdest = true; |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void Close() |
|
|
|
|
{ |
|
|
|
|
if(conn != nullptr) PQfinish(conn); |
|
|
|
|
conn = nullptr; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
virtual bool Put(const MString& key, const MString& value, size_t ttl) const override |
|
|
|
|
{ |
|
|
|
|
if(!CheckCon()) return false; |
|
|
|
@ -235,14 +363,7 @@ class PostgreSQLCache: public GenericCache
|
|
|
|
|
return {std::move(val), true}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
virtual ~PostgreSQLCache() override |
|
|
|
|
{ |
|
|
|
|
if(!CheckCon()) return; |
|
|
|
|
|
|
|
|
|
auto* res = PQexec(conn, "DELETE FROM cache WHERE exptime<localtimestamp;"); |
|
|
|
|
PQclear(res); |
|
|
|
|
Close(); |
|
|
|
|
} |
|
|
|
|
virtual ~PostgreSQLCache() override = default; |
|
|
|
|
|
|
|
|
|
explicit operator bool() const { return conn != nullptr; } |
|
|
|
|
}; |
|
|
|
@ -262,14 +383,14 @@ inline GenericCache* CreateCache(const MString& cachedesc)
|
|
|
|
|
if(name == "sqlite") |
|
|
|
|
{ |
|
|
|
|
auto ret = new SQLiteCache; |
|
|
|
|
ret->Init(par); |
|
|
|
|
ret->Init(); |
|
|
|
|
if(*ret) return ret; |
|
|
|
|
delete ret; |
|
|
|
|
} |
|
|
|
|
if(name == "postgre" || name == "postgres" || name == "postgresql") |
|
|
|
|
{ |
|
|
|
|
auto ret = new PostgreSQLCache; |
|
|
|
|
ret->Init(par); |
|
|
|
|
ret->Init(); |
|
|
|
|
if(*ret) return ret; |
|
|
|
|
delete ret; |
|
|
|
|
} |
|
|
|
|