#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