module db; import std.algorithm; import std.array; import std.typecons; import std.conv; import d2sqlite3; import slf4d; import handy_httpd.components.optional; struct Column { const string name; } Database getDb() { import std.file; bool shouldInitDb = !exists("teacher-tools.db"); int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; if (d2sqlite3.threadSafe()) { flags |= SQLITE_OPEN_NOMUTEX; } Database db = Database("teacher-tools.db", flags); db.execute("PRAGMA foreign_keys=ON"); const string schema = import("schema.sql"); if (shouldInitDb) { db.run(schema); info("Initialized database schema."); } return db; } private string[] getColumnNames(T)() { import std.string : toLower; alias members = __traits(allMembers, T); string[members.length] columnNames; static foreach (i; 0 .. members.length) { static if (__traits(getAttributes, __traits(getMember, T, members[i])).length > 0) { columnNames[i] = toLower(__traits(getAttributes, __traits(getMember, T, members[i]))[0].name); } else { columnNames[i] = toLower(members[i]); } } return columnNames.dup; } private string getArgsStr(T)() { import std.traits : RepresentationTypeTuple; alias types = RepresentationTypeTuple!T; string argsStr = ""; static foreach (i, type; types) { argsStr ~= "row.peek!(" ~ type.stringof ~ ")(" ~ i.to!string ~ ")"; static if (i + 1 < types.length) { argsStr ~= ", "; } } return argsStr; } T parseRow(T)(Row row) { mixin("T t = T(" ~ getArgsStr!T ~ ");"); return t; } T[] findAll(T, Args...)(Database db, string query, Args args) { Statement stmt = db.prepare(query); stmt.bindAll(args); ResultRange result = stmt.execute(); return result.map!(row => parseRow!T(row)).array; } Optional!T findOne(T, Args...)(Database db, string query, Args args) { Statement stmt = db.prepare(query); stmt.bindAll(args); ResultRange result = stmt.execute(); if (result.empty) return Optional!T.empty; return Optional!T.of(parseRow!T(result.front)); }