#include "sqlite.hpp" namespace ledger::sqlite { constexpr const char *LEDGER_TABLE = "ledger"; constexpr const char *LEDGER_COLUMNS = "seq_no, time, ledger_hash, prev_ledger_hash, data_hash, state_hash, patch_hash, user_hash, input_hash, output_hash"; constexpr const char *COLUMN_DATA_TYPES[]{"INT", "TEXT"}; constexpr const char *CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "; constexpr const char *INSERT_INTO = "INSERT INTO "; constexpr const char *PRIMARY_KEY = "PRIMARY KEY"; constexpr const char *NOT_NULL = "NOT NULL"; constexpr const char *VALUES = "VALUES"; constexpr const char *SELECT_ALL = "SELECT * FROM "; constexpr const char *SQLITE_MASTER = "sqlite_master"; constexpr const char *WHERE = " WHERE "; constexpr const char *ORDER_BY = " ORDER BY "; constexpr const char *AND = " AND "; /** * Opens a connection to a given databse and give the db pointer. * @param db_name Database name to be connected. * @param db Pointer to the db pointer which is to be connected and pointed. * @returns returns 0 on success, or -1 on error. */ int open_db(std::string_view db_name, sqlite3 **db) { if (sqlite3_open(db_name.data(), db) != SQLITE_OK) { *db = NULL; LOG_ERROR << "Can't open database: " << sqlite3_errmsg(*db); return -1; } return 0; } /** * Executes given sql query. * @param db Pointer to the db. * @param sql Sql query to be executed. * @param callback Callback funcion which is called for each result row. * @param callback_first_arg First data argumat to be parced to the callback (void pointer). * @returns returns 0 on success, or -1 on error. */ int exec_sql(sqlite3 *db, std::string_view sql, int (*callback)(void *, int, char **, char **), void *callback_first_arg) { char *err_msg; if (sqlite3_exec(db, sql.data(), callback, (callback != NULL ? (void *)callback_first_arg : NULL), &err_msg) != SQLITE_OK) { LOG_ERROR << "SQL error occured: " << err_msg; sqlite3_free(err_msg); return -1; } return 0; } /** * Create a table with given table info. * @param db Pointer to the db. * @param table_name Table name to be created. * @param column_info Column info of the table. * @returns returns 0 on success, or -1 on error. */ int create_table(sqlite3 *db, std::string_view table_name, const std::vector &column_info) { std::string sql; sql.append(CREATE_TABLE); sql.append(table_name); sql.append(" ("); for (auto itr = column_info.begin(); itr != column_info.end(); ++itr) { sql.append(itr->name); sql.append(" "); sql.append(COLUMN_DATA_TYPES[itr->column_type]); if (itr->is_key) { sql.append(" "); sql.append(PRIMARY_KEY); } if (!itr->is_null) { sql.append(" "); sql.append(NOT_NULL); } if (itr != column_info.end() - 1) sql.append(","); } sql.append(")"); /* Execute SQL statement */ return exec_sql(db, sql); } /** * Insert values to a table. * @param db Pointer to the db. * @param table_name Table name to be populated. * @param column_names_string Comma seperated string of colums (eg: "col_1,col_2,..."). * @param value_strings Vector of comma seperated values (wrap in single quotes for TEXT type) (eg: ["r1val1,'r1val2',...", "r2val1,'r2val2',..."]). * @returns returns 0 on success, or -1 on error. */ int insert_values(sqlite3 *db, std::string_view table_name, std::string_view column_names_string, const std::vector &value_strings) { std::string sql; sql.append(INSERT_INTO); sql.append(table_name); sql.append("("); sql.append(column_names_string); sql.append(") "); sql.append(VALUES); for (auto itr = value_strings.begin(); itr != value_strings.end(); ++itr) { sql.append("("); sql.append(*itr); sql.append(")"); if (itr != value_strings.end() - 1) sql.append(","); } /* Execute SQL statement */ return exec_sql(db, sql); } /** * Insert a value row to a table. * @param db Pointer to the db. * @param table_name Table name to be populated. * @param column_names_string Comma seperated string of colums (eg: "col_1,col_2,..."). * @param value_string comma seperated values as per column order (wrap in single quotes for TEXT type) (eg: "r1val1,'r1val2',..."). * @returns returns 0 on success, or -1 on error. */ int insert_value(sqlite3 *db, std::string_view table_name, std::string_view column_names_string, std::string_view value_string) { std::string sql; // Reserving the space for the query before construction. sql.reserve(sizeof(INSERT_INTO) + table_name.size() + column_names_string.size() + sizeof(VALUES) + value_string.size() + 5); sql.append(INSERT_INTO); sql.append(table_name); sql.append("("); sql.append(column_names_string); sql.append(") "); sql.append(VALUES); sql.append("("); sql.append(value_string); sql.append(")"); /* Execute SQL statement */ return exec_sql(db, sql); } /** * Checks whether table exist in the database. * @param db Pointer to the db. * @param table_name Table name to be checked. * @returns returns true is exist, otherwise false. */ bool is_table_exists(sqlite3 *db, std::string_view table_name) { std::string sql; // Reserving the space for the query before construction. sql.reserve(sizeof(SELECT_ALL) + sizeof(SQLITE_MASTER) + sizeof(WHERE) + sizeof(AND) + table_name.size() + 19); sql.append(SELECT_ALL); sql.append(SQLITE_MASTER); sql.append(WHERE); sql.append("type='table'"); sql.append(AND); sql.append("name='"); sql.append(table_name); sql.append("'"); sqlite3_stmt *stmt; if (sqlite3_prepare_v2(db, sql.data(), -1, &stmt, 0) == SQLITE_OK && stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW) { // Finalize and distroys the statement. sqlite3_finalize(stmt); return true; } // Finalize and distroys the statement. sqlite3_finalize(stmt); return false; } /** * Closes a connection to a given databse. * @param db Pointer to the db. * @returns returns 0 on success, or -1 on error. */ int close_db(sqlite3 **db) { if (sqlite3_close(*db) != SQLITE_OK) { LOG_ERROR << "Can't close database: " << sqlite3_errmsg(*db); return -1; } *db = NULL; return 0; } /** * Creates a table for ledger records. * @param db Pointer to the db. * @returns returns 0 on success, or -1 on error. */ int create_ledger_table(sqlite3 *db) { std::vector column_info{ table_column_info("seq_no", COLUMN_DATA_TYPE::INT, true), table_column_info("time", COLUMN_DATA_TYPE::INT), table_column_info("ledger_hash", COLUMN_DATA_TYPE::TEXT), table_column_info("prev_ledger_hash", COLUMN_DATA_TYPE::TEXT), table_column_info("data_hash", COLUMN_DATA_TYPE::TEXT), table_column_info("state_hash", COLUMN_DATA_TYPE::TEXT), table_column_info("patch_hash", COLUMN_DATA_TYPE::TEXT), table_column_info("user_hash", COLUMN_DATA_TYPE::TEXT), table_column_info("input_hash", COLUMN_DATA_TYPE::TEXT), table_column_info("output_hash", COLUMN_DATA_TYPE::TEXT)}; if (create_table(db, LEDGER_TABLE, column_info) == -1) return -1; return 0; } /** * Inserts a ledger record. * @param db Pointer to the db. * @param ledger Ledger struct to be inserted. * @returns returns 0 on success, or -1 on error. */ int insert_ledger_row(sqlite3 *db, const ledger &ledger) { std::string value_string = std::to_string(ledger.seq_no) + "," + std::to_string(ledger.time) + "," + "'" + ledger.ledger_hash_hex + "'," + "'" + ledger.prev_ledger_hash_hex + "'," + "'" + ledger.data_hash_hex + "'," + "'" + ledger.state_hash_hex + "'," + "'" + ledger.patch_hash_hex + "'," + "'" + ledger.user_hash_hex + "'," + "'" + ledger.input_hash_hex + "'," + "'" + ledger.output_hash_hex + "'"; if (insert_value(db, LEDGER_TABLE, LEDGER_COLUMNS, value_string) == -1) return -1; return 0; } /** * Checks whether ledger table exist. * @param db Pointer to the db. * @returns returns true is exist, otherwise false. */ bool is_ledger_table_exist(sqlite3 *db) { return is_table_exists(db, LEDGER_TABLE); } /** * Get the last ledger record of the given db. * @param db Pointer to the db. * @returns returns the last ledger as a struct. */ ledger get_last_ledger(sqlite3 *db) { std::string sql; sql.append(SELECT_ALL); sql.append(LEDGER_TABLE); sql.append(ORDER_BY); sql.append("seq_no DESC LIMIT 1"); sqlite3_stmt *stmt; sqlite::ledger ledger; if (sqlite3_prepare_v2(db, sql.data(), -1, &stmt, 0) == SQLITE_OK && stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW) { ledger.seq_no = sqlite3_column_int64(stmt, 0); ledger.time = sqlite3_column_int64(stmt, 1); ledger.ledger_hash_hex = std::string((char *)sqlite3_column_text(stmt, 2)); ledger.prev_ledger_hash_hex = std::string((char *)sqlite3_column_text(stmt, 3)); ledger.data_hash_hex = std::string((char *)sqlite3_column_text(stmt, 4)); ledger.state_hash_hex = std::string((char *)sqlite3_column_text(stmt, 5)); ledger.patch_hash_hex = std::string((char *)sqlite3_column_text(stmt, 6)); ledger.user_hash_hex = std::string((char *)sqlite3_column_text(stmt, 7)); ledger.input_hash_hex = std::string((char *)sqlite3_column_text(stmt, 8)); ledger.output_hash_hex = std::string((char *)sqlite3_column_text(stmt, 9)); } // Finalize and distroys the statement. sqlite3_finalize(stmt); return ledger; } } // namespace ledger::sqlite