initial commit
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
#pragma once
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include <map>
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Exceptions
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
struct DbError : std::runtime_error {
|
||||
int code;
|
||||
DbError(int rc, const std::string& msg)
|
||||
: std::runtime_error(msg), code(rc) {}
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Value : type SQL générique
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
using DbNull = std::monostate;
|
||||
using DbBlob = std::vector<uint8_t>;
|
||||
using DbValue = std::variant<DbNull, int64_t, double, std::string, DbBlob>;
|
||||
|
||||
inline DbValue db_null() { return DbNull{}; }
|
||||
inline DbValue db_int (int64_t v) { return v; }
|
||||
inline DbValue db_real(double v) { return v; }
|
||||
inline DbValue db_text(const std::string& v) { return v; }
|
||||
inline DbValue db_blob(const DbBlob& v) { return v; }
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// ResultRow : ligne copiée depuis un statement
|
||||
// (valeurs extraites, safe après sqlite3_reset)
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
struct ResultRow {
|
||||
std::vector<std::string> names;
|
||||
std::vector<DbValue> values;
|
||||
|
||||
int size() const { return static_cast<int>(values.size()); }
|
||||
|
||||
const DbValue& get(int col) const { return values.at(col); }
|
||||
const DbValue& get(const std::string& name) const {
|
||||
for (int i = 0; i < static_cast<int>(names.size()); ++i)
|
||||
if (names[i] == name) return values[i];
|
||||
throw std::out_of_range("Column not found: " + name);
|
||||
}
|
||||
|
||||
int64_t get_int (int col, int64_t def = 0) const { auto& v = get(col); return std::holds_alternative<int64_t>(v) ? std::get<int64_t>(v) : def; }
|
||||
double get_real(int col, double def = 0.0) const { auto& v = get(col); return std::holds_alternative<double>(v) ? std::get<double>(v) : def; }
|
||||
std::string get_text(int col, std::string def = {}) const { auto& v = get(col); return std::holds_alternative<std::string>(v) ? std::get<std::string>(v) : def; }
|
||||
bool is_null (int col) const { return std::holds_alternative<DbNull>(get(col)); }
|
||||
|
||||
int64_t get_int (const std::string& n, int64_t def = 0) const { return get_int (col_idx(n), def); }
|
||||
double get_real(const std::string& n, double def = 0.0) const { return get_real(col_idx(n), def); }
|
||||
std::string get_text(const std::string& n, std::string def = {}) const { return get_text(col_idx(n), def); }
|
||||
bool is_null (const std::string& n) const { return is_null (col_idx(n)); }
|
||||
|
||||
private:
|
||||
int col_idx(const std::string& name) const {
|
||||
for (int i = 0; i < static_cast<int>(names.size()); ++i)
|
||||
if (names[i] == name) return i;
|
||||
throw std::out_of_range("Column not found: " + name);
|
||||
}
|
||||
};
|
||||
|
||||
// Helper interne : copie une ligne depuis un stmt actif
|
||||
inline ResultRow snapshot_row(sqlite3_stmt* stmt) {
|
||||
ResultRow row;
|
||||
int n = sqlite3_column_count(stmt);
|
||||
row.names.reserve(n);
|
||||
row.values.reserve(n);
|
||||
for (int col = 0; col < n; ++col) {
|
||||
row.names.push_back(sqlite3_column_name(stmt, col));
|
||||
switch (sqlite3_column_type(stmt, col)) {
|
||||
case SQLITE_INTEGER: row.values.push_back(static_cast<int64_t>(sqlite3_column_int64(stmt, col))); break;
|
||||
case SQLITE_FLOAT: row.values.push_back(sqlite3_column_double(stmt, col)); break;
|
||||
case SQLITE_TEXT: {
|
||||
auto* p = sqlite3_column_text(stmt, col);
|
||||
row.values.push_back(std::string(reinterpret_cast<const char*>(p)));
|
||||
break;
|
||||
}
|
||||
case SQLITE_BLOB: {
|
||||
auto* p = static_cast<const uint8_t*>(sqlite3_column_blob(stmt, col));
|
||||
int nb = sqlite3_column_bytes(stmt, col);
|
||||
row.values.push_back(DbBlob(p, p + nb));
|
||||
break;
|
||||
}
|
||||
default: row.values.push_back(DbNull{}); break;
|
||||
}
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Statement : requête préparée réutilisable
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
class Statement {
|
||||
public:
|
||||
Statement() = default;
|
||||
Statement(const Statement&) = delete;
|
||||
Statement& operator=(const Statement&) = delete;
|
||||
Statement(Statement&& o) noexcept : stmt_(o.stmt_) { o.stmt_ = nullptr; }
|
||||
Statement& operator=(Statement&& o) noexcept {
|
||||
finalize(); stmt_ = o.stmt_; o.stmt_ = nullptr; return *this;
|
||||
}
|
||||
~Statement() { finalize(); }
|
||||
|
||||
bool valid() const { return stmt_ != nullptr; }
|
||||
|
||||
// ── Bind par index (1-based) ──────────────────────────────
|
||||
Statement& bind(int i, DbNull) { sqlite3_bind_null (stmt_, i); return *this; }
|
||||
Statement& bind(int i, int64_t v) { sqlite3_bind_int64 (stmt_, i, v); return *this; }
|
||||
Statement& bind(int i, int v) { return bind(i, static_cast<int64_t>(v)); }
|
||||
Statement& bind(int i, double v) { sqlite3_bind_double(stmt_, i, v); return *this; }
|
||||
Statement& bind(int i, const std::string& v) { sqlite3_bind_text (stmt_, i, v.c_str(), -1, SQLITE_TRANSIENT); return *this; }
|
||||
Statement& bind(int i, const char* v) { sqlite3_bind_text (stmt_, i, v, -1, SQLITE_TRANSIENT); return *this; }
|
||||
Statement& bind(int i, const DbBlob& v) { sqlite3_bind_blob (stmt_, i, v.data(), static_cast<int>(v.size()), SQLITE_TRANSIENT); return *this; }
|
||||
Statement& bind(int i, const DbValue& v) { std::visit([&](auto&& x){ this->bind(i, x); }, v); return *this; }
|
||||
|
||||
// ── Bind par nom (:nom, $nom, @nom) ──────────────────────
|
||||
template<typename T>
|
||||
Statement& bind(const std::string& name, T&& v) {
|
||||
return bind(param_index(name), std::forward<T>(v));
|
||||
}
|
||||
|
||||
// ── Exécution ────────────────────────────────────────────
|
||||
|
||||
// Itère sur chaque ligne copiée (safe, row valide après reset)
|
||||
int each(std::function<void(const ResultRow&)> cb) {
|
||||
int count = 0;
|
||||
while (true) {
|
||||
int rc = sqlite3_step(stmt_);
|
||||
if (rc == SQLITE_ROW) { cb(snapshot_row(stmt_)); ++count; }
|
||||
else if (rc == SQLITE_DONE) { break; }
|
||||
else { sqlite3_reset(stmt_); throw DbError(rc, sqlite3_errmsg(sqlite3_db_handle(stmt_))); }
|
||||
}
|
||||
sqlite3_reset(stmt_);
|
||||
return count;
|
||||
}
|
||||
|
||||
// Première ligne (ou nullopt)
|
||||
std::optional<ResultRow> first() {
|
||||
std::optional<ResultRow> result;
|
||||
int rc = sqlite3_step(stmt_);
|
||||
if (rc == SQLITE_ROW) result = snapshot_row(stmt_);
|
||||
sqlite3_reset(stmt_);
|
||||
if (rc != SQLITE_ROW && rc != SQLITE_DONE)
|
||||
throw DbError(rc, sqlite3_errmsg(sqlite3_db_handle(stmt_)));
|
||||
return result;
|
||||
}
|
||||
|
||||
// INSERT / UPDATE / DELETE — renvoie le nb de lignes affectées
|
||||
int exec() {
|
||||
int rc = sqlite3_step(stmt_);
|
||||
sqlite3_reset(stmt_);
|
||||
if (rc != SQLITE_DONE && rc != SQLITE_ROW)
|
||||
throw DbError(rc, sqlite3_errmsg(sqlite3_db_handle(stmt_)));
|
||||
return sqlite3_changes(sqlite3_db_handle(stmt_));
|
||||
}
|
||||
|
||||
Statement& clear_bindings() { sqlite3_clear_bindings(stmt_); return *this; }
|
||||
|
||||
private:
|
||||
friend class Database;
|
||||
sqlite3_stmt* stmt_ = nullptr;
|
||||
explicit Statement(sqlite3_stmt* s) : stmt_(s) {}
|
||||
void finalize() { if (stmt_) { sqlite3_finalize(stmt_); stmt_ = nullptr; } }
|
||||
|
||||
int param_index(const std::string& name) const {
|
||||
int i = sqlite3_bind_parameter_index(stmt_, name.c_str());
|
||||
if (i == 0) throw std::out_of_range("Parameter not found: " + name);
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Database : gestion de la connexion
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
class Database {
|
||||
public:
|
||||
explicit Database(const std::string& path,
|
||||
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) {
|
||||
int rc = sqlite3_open_v2(path.c_str(), &db_, flags, nullptr);
|
||||
if (rc != SQLITE_OK) {
|
||||
std::string msg = db_ ? sqlite3_errmsg(db_) : "sqlite3_open_v2 failed";
|
||||
sqlite3_close(db_);
|
||||
throw DbError(rc, msg);
|
||||
}
|
||||
exec("PRAGMA journal_mode=WAL");
|
||||
exec("PRAGMA foreign_keys=ON");
|
||||
}
|
||||
|
||||
~Database() { if (db_) sqlite3_close(db_); }
|
||||
Database(const Database&) = delete;
|
||||
Database& operator=(const Database&) = delete;
|
||||
Database(Database&& o) noexcept : db_(o.db_) { o.db_ = nullptr; }
|
||||
|
||||
// ── Requête préparée ──────────────────────────────────────
|
||||
Statement prepare(const std::string& sql) {
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK)
|
||||
throw DbError(rc, sqlite3_errmsg(db_));
|
||||
return Statement(stmt);
|
||||
}
|
||||
|
||||
// ── Exécution directe (sans résultat) ────────────────────
|
||||
void exec(const std::string& sql) {
|
||||
char* err = nullptr;
|
||||
int rc = sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &err);
|
||||
if (rc != SQLITE_OK) {
|
||||
std::string msg = err ? err : "exec failed";
|
||||
sqlite3_free(err);
|
||||
throw DbError(rc, msg);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Transaction RAII ─────────────────────────────────────
|
||||
struct Transaction {
|
||||
Database& db;
|
||||
bool done = false;
|
||||
explicit Transaction(Database& db) : db(db) { db.exec("BEGIN"); }
|
||||
~Transaction() { if (!done) db.exec("ROLLBACK"); }
|
||||
void commit() { db.exec("COMMIT"); done = true; }
|
||||
void rollback() { db.exec("ROLLBACK"); done = true; }
|
||||
};
|
||||
|
||||
Transaction begin() { return Transaction(*this); }
|
||||
|
||||
// ── Utilitaires ──────────────────────────────────────────
|
||||
int64_t last_insert_rowid() const { return sqlite3_last_insert_rowid(db_); }
|
||||
int changes() const { return sqlite3_changes(db_); }
|
||||
sqlite3* handle() { return db_; }
|
||||
|
||||
private:
|
||||
sqlite3* db_ = nullptr;
|
||||
};
|
||||
@@ -0,0 +1,262 @@
|
||||
#include "Database.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using json = nlohmann::json;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Structure : nom d'instance décomposé depuis le nom de fichier
|
||||
// Format : Site_SiteCr_Tracks_Rames_Jobs_ID
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
struct InstanceKey {
|
||||
int64_t site;
|
||||
int64_t site_cr;
|
||||
int64_t tracks;
|
||||
int64_t rames;
|
||||
int64_t jobs;
|
||||
int64_t id;
|
||||
std::string raw; // nom complet sans extension
|
||||
};
|
||||
|
||||
InstanceKey parse_instance_name(const std::string& stem) {
|
||||
InstanceKey k;
|
||||
k.raw = stem;
|
||||
std::vector<int64_t> parts;
|
||||
std::string token;
|
||||
for (char c : stem) {
|
||||
if (c == '_') {
|
||||
if (!token.empty()) { parts.push_back(std::stoll(token)); token.clear(); }
|
||||
} else {
|
||||
token += c;
|
||||
}
|
||||
}
|
||||
if (!token.empty()) parts.push_back(std::stoll(token));
|
||||
|
||||
if (parts.size() != 6)
|
||||
throw std::runtime_error("Nom d'instance invalide (attendu 6 entiers) : " + stem);
|
||||
|
||||
k.site = parts[0];
|
||||
k.site_cr = parts[1];
|
||||
k.tracks = parts[2];
|
||||
k.rames = parts[3];
|
||||
k.jobs = parts[4];
|
||||
k.id = parts[5];
|
||||
return k;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Création des tables
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
void create_tables(Database& db) {
|
||||
db.exec(R"(
|
||||
CREATE TABLE IF NOT EXISTS instances (
|
||||
instance_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
site INTEGER NOT NULL,
|
||||
site_cr INTEGER NOT NULL,
|
||||
tracks INTEGER NOT NULL,
|
||||
rames INTEGER NOT NULL,
|
||||
jobs INTEGER NOT NULL,
|
||||
id INTEGER NOT NULL
|
||||
)
|
||||
)");
|
||||
|
||||
db.exec(R"(
|
||||
CREATE TABLE IF NOT EXISTS results (
|
||||
result_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
instance_id INTEGER NOT NULL REFERENCES instances(instance_id),
|
||||
algo TEXT NOT NULL,
|
||||
fct_objectif INTEGER NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
time_final REAL NOT NULL,
|
||||
best_alg TEXT,
|
||||
UNIQUE(instance_id, algo)
|
||||
)
|
||||
)");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Insertion / récupération d'une instance (upsert)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
int64_t upsert_instance(Database& /*db*/,
|
||||
Statement& ins_inst,
|
||||
Statement& sel_inst,
|
||||
const InstanceKey& k) {
|
||||
ins_inst.bind(":name", k.raw)
|
||||
.bind(":site", k.site)
|
||||
.bind(":site_cr", k.site_cr)
|
||||
.bind(":tracks", k.tracks)
|
||||
.bind(":rames", k.rames)
|
||||
.bind(":jobs", k.jobs)
|
||||
.bind(":id", k.id)
|
||||
.exec();
|
||||
|
||||
auto row = sel_inst.bind(":name", k.raw).first();
|
||||
if (!row) throw std::runtime_error("Instance introuvable après insertion : " + k.raw);
|
||||
return row->get_int("instance_id");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Parse un fichier JSON et insère les résultats
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
void process_file(const fs::path& path,
|
||||
Database& db,
|
||||
Statement& ins_inst,
|
||||
Statement& sel_inst,
|
||||
Statement& ins_result) {
|
||||
// -- Nom d'instance depuis le stem du fichier
|
||||
InstanceKey key = parse_instance_name(path.stem().string());
|
||||
int64_t inst_id = upsert_instance(db, ins_inst, sel_inst, key);
|
||||
|
||||
// -- Lecture JSON
|
||||
std::ifstream f(path);
|
||||
if (!f) throw std::runtime_error("Impossible d'ouvrir : " + path.string());
|
||||
|
||||
json doc;
|
||||
try { f >> doc; }
|
||||
catch (const json::exception& e) {
|
||||
throw std::runtime_error("JSON invalide dans " + path.string() + " : " + e.what());
|
||||
}
|
||||
|
||||
if (!doc.is_object())
|
||||
throw std::runtime_error("Le JSON doit être un objet à la racine : " + path.string());
|
||||
|
||||
// -- Chaque clé = un algo
|
||||
for (auto& [algo_name, obj] : doc.items()) {
|
||||
if (!obj.is_object()) continue;
|
||||
|
||||
if(obj.contains("fctObjectif"))
|
||||
{
|
||||
int64_t fct_obj = obj.value("fctObjectif", int64_t{0});
|
||||
std::string status = obj.value("status", std::string{});
|
||||
double time_f = obj.value("timeFinal", 0.0);
|
||||
std::string best_alg = obj.value("bestAlg", algo_name);
|
||||
|
||||
ins_result.bind(":instance_id", inst_id)
|
||||
.bind(":algo", algo_name)
|
||||
.bind(":fct_objectif", fct_obj)
|
||||
.bind(":status", status)
|
||||
.bind(":time_final", time_f)
|
||||
.bind(":best_alg", best_alg)
|
||||
.exec();
|
||||
}
|
||||
else {
|
||||
int64_t fct_obj = obj.value("obj", int64_t{0});
|
||||
std::string status = "Heuristic";
|
||||
double time_f = obj.value("timeFinal", 0.0);
|
||||
std::string best_alg = obj.value("source", std::string{});
|
||||
|
||||
ins_result.bind(":instance_id", inst_id)
|
||||
.bind(":algo", algo_name)
|
||||
.bind(":fct_objectif", fct_obj)
|
||||
.bind(":status", status)
|
||||
.bind(":time_final", time_f)
|
||||
.bind(":best_alg", best_alg)
|
||||
.exec();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Collecte récursive des .json dans un dossier
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
std::vector<fs::path> collect_json_files(const fs::path& dir) {
|
||||
std::vector<fs::path> files;
|
||||
if (!fs::is_directory(dir)) {
|
||||
std::cerr << "[WARN] Pas un dossier, ignoré : " << dir << "\n";
|
||||
return files;
|
||||
}
|
||||
for (auto& entry : fs::recursive_directory_iterator(dir)) {
|
||||
if (entry.is_regular_file() && entry.path().extension() == ".json")
|
||||
files.push_back(entry.path());
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Main
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "Usage: " << argv[0] << " <resultats.db> <dossier1> [dossier2 ...]\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string db_path = argv[1];
|
||||
std::vector<fs::path> dirs;
|
||||
for (int i = 2; i < argc; ++i) dirs.emplace_back(argv[i]);
|
||||
|
||||
// -- Collecte des fichiers
|
||||
std::vector<fs::path> all_files;
|
||||
for (auto& d : dirs) {
|
||||
auto files = collect_json_files(d);
|
||||
all_files.insert(all_files.end(), files.begin(), files.end());
|
||||
}
|
||||
|
||||
if (all_files.empty()) {
|
||||
std::cerr << "[INFO] Aucun fichier .json trouvé.\n";
|
||||
return 0;
|
||||
}
|
||||
std::cout << "[INFO] " << all_files.size() << " fichier(s) trouvé(s).\n";
|
||||
|
||||
try {
|
||||
Database db(db_path);
|
||||
create_tables(db);
|
||||
|
||||
// Requêtes préparées — compilées une seule fois
|
||||
auto ins_inst = db.prepare(R"(
|
||||
INSERT OR IGNORE INTO instances (name, site, site_cr, tracks, rames, jobs, id)
|
||||
VALUES (:name, :site, :site_cr, :tracks, :rames, :jobs, :id)
|
||||
)");
|
||||
auto sel_inst = db.prepare(
|
||||
"SELECT instance_id FROM instances WHERE name = :name"
|
||||
);
|
||||
auto ins_result = db.prepare(R"(
|
||||
INSERT OR REPLACE INTO results
|
||||
(instance_id, algo, fct_objectif, status, time_final, best_alg)
|
||||
VALUES
|
||||
(:instance_id, :algo, :fct_objectif, :status, :time_final, :best_alg)
|
||||
)");
|
||||
|
||||
int ok = 0, err = 0;
|
||||
|
||||
// Une transaction par lot = bien plus rapide qu'une transaction par fichier
|
||||
auto tx = db.begin();
|
||||
for (auto& path : all_files) {
|
||||
try {
|
||||
process_file(path, db, ins_inst, sel_inst, ins_result);
|
||||
++ok;
|
||||
if (ok % 100 == 0)
|
||||
std::cout << " [" << ok << "/" << all_files.size() << "]\n";
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "[ERR] " << path.filename() << " : " << e.what() << "\n";
|
||||
++err;
|
||||
}
|
||||
}
|
||||
tx.commit();
|
||||
|
||||
std::cout << "[DONE] " << ok << " ok, " << err << " erreur(s).\n";
|
||||
|
||||
} catch (const DbError& e) {
|
||||
std::cerr << "[DB ERR " << e.code << "] " << e.what() << "\n";
|
||||
return 1;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "[ERR] " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user