microwerx icon

SQLite Example in C++

microwerx | PRO | 04/16/20 08:56:40 PM UTC | 0 ⭐ | 1153 👁️ | Never ⏰ | []
C++ |

9.45 KB

|

None

|

0 👍

/

0 👎

#ifdef _MSC_VER
#include <winsqlite/winsqlite3.h>
#else
// On UNIX systems, make sure libsqlite3 is installed
// Then compile and link the sqlite3 library with your source code
// $ c++ labsqlite.cpp -lsqlite3
#include <sqlite3.h>
#endif
#include <iostream>
#include <iomanip>
#include <string>
 
#pragma comment(lib, "winsqlite3.lib")
 
class PreparedStmt {
public:
    // Initialize the prepared statement and reset it prepare it for use
    PreparedStmt(sqlite3_stmt* stmt) : stmt(stmt) { reset(); }
 
    // Finalize the prepared statement upon completion
    ~PreparedStmt() { if (stmt) finalize(); }
 
    // Overload the bool conversion operator to return whether the query can be used
    operator bool() const { return (stmt != nullptr && readyToStep); }
 
    // Finalize the prepared statement which makes it unusable for any future use.
    void finalize() {
        if (!stmt) {
            std::cerr << "the program is trying to finalize an already finalized prepared statement!\n";
            return;
        }
        sqlite3_finalize(stmt);
        stmt = nullptr;
    }
 
    // Reset the statement so that the SQL query can be executed again.
    bool reset() {
        if (!stmt) return false;
        if (stmt) readyToStep = true;
        if (firstRun) {
            firstRun = true;
            return true;
        }
        sqlite3_reset(stmt);
        return readyToStep;
    }
 
    // Execute the statement -- returns true if a row of information is ready to be processed
    bool step() {
        if (!readyToStep) return false;
        int rc = sqlite3_step(stmt);
        if (rc == SQLITE_ROW) return true;
        // If an error occured or step returned DONE, we need to call reset next time
        readyToStep = false;
        if (rc == SQLITE_DONE) return true;
        return false;
    }
 
    // Return an integer from column index. Index is 0-based.
    int columnInt(int index) {
        if (!stmt) return 0;
        return sqlite3_column_int(stmt, index);
    }
 
    // Return an integer from column index. Index is 0-based.
    long long columnInt64(int index) {
        if (!stmt) return 0;
        return sqlite3_column_int64(stmt, index);
    }
 
    // Return an double from column index. Index is 0-based.
    double columnReal(int index) {
        if (!stmt) return 0.0;
        return sqlite3_column_double(stmt, index);
    }
 
    // Return an string from column index. Index is 0-based.
    std::string columnText(int index) {
        std::string text;
        if (!stmt) return text;
        const char* textptr = (const char*)sqlite3_column_text(stmt, index);
        if (textptr) text = textptr;
        return text;
    }
 
    // Sets query element to an integer. Index is 1-based.
    void bindInt(int index, int value) {
        sqlite3_bind_int(stmt, index, value);
    }
 
    // Sets query element to an integer. Index is 1-based.
    void bindInt64(int index, long long value) {
        sqlite3_bind_int64(stmt, index, value);
    }
 
    // Sets query element to a double. Index is 1-based.
    void bindReal(int index, double value) {
        sqlite3_bind_double(stmt, index, value);
    }
 
    // Sets query element to a string. Index is 1-based.
    void bindText(int index, const std::string& value) {
        sqlite3_bind_text(stmt, index, value.c_str(), (int)value.size(), nullptr);
    }
 
private:
    bool firstRun{ true };
    bool readyToStep{ false };
    sqlite3_stmt* stmt{ nullptr };
};
 
 
class Database {
public:
    Database();
    ~Database();
 
    // Overload the bool conversion operator to return whether the database can be used
    operator bool() { return db_ != nullptr; }
 
    // Open the database file. If database = ":memory:", creates a memory only database. If readonly is true, the file database is loaded as readonly.
    bool open(const std::string& database, bool readonly = false);
 
    // Close the database file
    void close();
 
    // Construct a PreparedStmt
    PreparedStmt prepare(const std::string& sql);
 
    // Execute a one off SQL query -- the internal callback is used to process the rows
    bool exec(const std::string& sql);
 
    // get last row inserted
    long long lastRowID();
 
private:
    // Handle an error by SQLite
    bool _handleError();
 
    // Callback just prints out the rows and columns for now
    static int SQLITE_CALLBACK _callback(void* pDatabase, int argc, char** argv, char** colname);
 
    sqlite3* db_{ nullptr };
};
 
 
Database::Database() {}
 
 
Database::~Database() { close(); }
 
 
bool Database::open(const std::string& database, bool readonly) {
    int flags = 0;
    if (database == ":memory:") {
        flags |= SQLITE_OPEN_MEMORY;
    }
    
    if (readonly) {
        flags |= SQLITE_OPEN_READONLY;
    }
    else {
        flags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
    }
    sqlite3_open_v2(database.c_str(), &db_, flags, nullptr);
    return _handleError();
}
 
 
void Database::close() {
    if (!db_) return;
 
    int rc = sqlite3_close_v2(db_);
    if (rc) _handleError();
}
 
 
bool Database::exec(const std::string& sql) {
    if (!db_) throw std::runtime_error("Database not open");
    char* errorMessage{ nullptr };
    sqlite3_exec(db_, sql.c_str(), Database::_callback, (void*)this, &errorMessage);
    if (errorMessage) {
        std::cerr << "SQLIte3 Error: " << errorMessage << "\n";
        sqlite3_free(errorMessage);
        return false;
    }
    return true;
}
 
 
long long Database::lastRowID() {
    return sqlite3_last_insert_rowid(db_);
}
 
 
PreparedStmt Database::prepare(const std::string& sql) {
    if (!db_) throw std::runtime_error("Database not open");
    sqlite3_stmt* stmt;
    int rc = sqlite3_prepare_v3(db_, sql.c_str(), sql.size(), 0, &stmt, nullptr);
    _handleError();
    return PreparedStmt(stmt);
}
 
 
int SQLITE_CALLBACK Database::_callback(void* pDatabase, int argc, char** argv, char** colname) {
    Database* db = (Database*)pDatabase;
 
    for (int i = 0; i < argc; i++) {
        if (!argv[i]) std::cout << "  --  NULL  --  ";
        else std::cout << std::setw(16) << argv[i] << " ";
    }
    std::cout << "\n";
    // returning non-0 results in SQLITE_ABORT
    return 0;
}
 
 
bool Database::_handleError() {
    if (sqlite3_errcode(db_) != SQLITE_OK) {
        std::cerr << "SQLite error: " << sqlite3_errmsg(db_) << "\n";
        return false;
    }
    return true;
}
 
 
bool UpdateRecord(Database& db, long long rowID, const std::string firstName, const std::string lastName) {
    if (!rowID) return false;
    {auto stmt = db.prepare("UPDATE Customers SET FirstName=?2 WHERE ID=?1");
    stmt.bindInt64(1, rowID);
    stmt.bindText(2, firstName);
    if (!stmt.step()) return false;
    }
    {auto stmt = db.prepare("UPDATE Customers SET LastName=?2 WHERE ID=?1");
    stmt.bindInt64(1, rowID);
    stmt.bindText(2, lastName);
    if (!stmt.step()) return false;
    }
    return true;
}
 
 
long long CreateRecord(Database& db, const std::string firstName, const std::string lastName) {
    // Divide this into two steps
    // Create the row (atomic operation)
    // Update the row
    long long rowID = 0;
    if (db.exec("INSERT INTO Customers (ID) VALUES (NULL)")) {
        rowID = db.lastRowID();
        UpdateRecord(db, rowID, firstName, lastName);
    }
    return rowID;
}
 
 
// Returns 0 if record does not exist
long long FindRecord(Database& db, const std::string firstName, const std::string lastName) {
    auto stmt = db.prepare("SELECT id FROM customers WHERE firstName LIKE ? AND lastName LIKE ?");
    if (!stmt) return 0;
    stmt.bindText(1, firstName);
    stmt.bindText(2, lastName);
    if (stmt.step()) {
        return stmt.columnInt64(0);
    }
    return 0;
}
 
 
bool DeleteRecord(Database& db, long long rowID) {
    auto stmt = db.prepare("DELETE FROM Customers WHERE ID=?");
    stmt.bindInt64(1, rowID);
    return stmt.step();
}
 
 
// Returns the number of records printed
int PrintRecords(Database& db, int first, int count) {
    // LIMIT is 0 based, so
    // - first=0 and count=25 means print the first 25 records
    // - first=25 and count=25 means print the next 25 records
    auto stmt = db.prepare("SELECT * FROM customers LIMIT ?1,?2");
    if (!stmt) return 0;
    stmt.bindInt(1, first);
    stmt.bindInt(2, count);
    std::cout << "      ID       FIRST NAME        LAST NAME\n";
    std::cout << "------------------------------------------\n";
    int rowCount = 0;
    while (stmt.step()) {
        rowCount++;
        std::cout << std::setw(8) << stmt.columnText(0) << " ";
        std::cout << std::setw(16) << stmt.columnText(1) << " ";
        std::cout << std::setw(16) << stmt.columnText(2) << "\n";
    }
    std::cout << "------------------------------------------\n";
    return rowCount;
}
 
 
int main(int argc, char** argv) {
    Database db;
    if (!db.open(":memory:")) return -1;
 
    if (!db.exec("SELECT COUNT(*) FROM Customers")) {
        db.exec("CREATE TABLE \"Customers\" ("
                "   \"ID\"  INTEGER PRIMARY KEY AUTOINCREMENT,"
                "   \"FirstName\"   TEXT,"
                "   \"LastName\"    TEXT"
                ")"
        );
        db.exec("INSERT INTO Customers (ID,FirstName,LastName) VALUES (NULL,'Jane','Austen')");
        db.exec("INSERT INTO Customers (ID,FirstName,LastName) VALUES (NULL,'Charles','Dickens')");
        db.exec("INSERT INTO Customers (ID,FirstName,LastName) VALUES (NULL,'Frank','Herbert')");
    }
 
    long long rowID{ 0 };
    if (rowID = FindRecord(db, "Jane", "Austen")) {
        std::cout << "Jane Austen is ID " << rowID << "\n";
        rowID = 0;
    }
 
    if (rowID = FindRecord(db, "George", "Elliot")) {
        std::cout << "George Elliot is ID " << rowID << "\n";
    }
    else {
        rowID = CreateRecord(db, "George", "Elliot");
        std::cout << "Created George Elliot record with ID " << rowID << "\n";
    }
 
    rowID = CreateRecord(db, "Mark", "Twain");
 
    std::cout << "\n";
    PrintRecords(db, 0, 25);
 
    // Change Mark Twain to Samuel Clemens
    if (rowID) {
        std::cout << "Updating Mark Twain\n";
        UpdateRecord(db, rowID, "Samuel", "Clemens");
    }
 
    std::cout << "\n";
    PrintRecords(db, 0, 25);
 
    // Delete Mark Twain
    if (rowID) DeleteRecord(db, rowID);
 
    return 0;
}

Comments