/******************************************************************************** * Copyright: (C) 2020 LingYun IoT System Studio * All rights reserved. * * Filename: database.c * Description: This library used to operate blob packet in sqlite database. * * Version: 1.0.0(2020年05月13日) * Author: Guo Wenxue * ChangeLog: 1, Release initial version on "2020年05月13日 12时14分23秒" * ********************************************************************************/ #include #include #include #include #include "database.h" #include "logger.h" /* Blob packet table name */ #define TABLE_NAME "PackTable" /* Use static global handler here in order to simplify the API, * But it will make this library not thread safe */ static sqlite3 *s_clidb = NULL; /* description: open or create sqlite database if not exist * input args: * $db_file: sqlite database file name * return value: <0: failure 0:ok * */ int db_open(const char *db_file) { char sql[SQL_COMMAND_LEN]={0}; char *errmsg = NULL; if( !db_file ) { log_error("%s() Invalid input arguments\n", __func__); return -1; } /* open database */ if( SQLITE_OK != sqlite3_open_v2(db_file, &s_clidb, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, NULL) ) { log_error("open/create database '%s' failed: %s\n", db_file, sqlite3_errmsg(s_clidb)); sqlite3_close(s_clidb); return -2; } /* --------------------------------------------- * SQLite performance & storage tuning * --------------------------------------------- */ /* Disable synchronous for higher performance */ sqlite3_exec(s_clidb, "pragma synchronous = OFF; ", NULL, NULL, NULL); /* enable full auto vacuum, Auto increase/decrease */ sqlite3_exec(s_clidb, "pragma auto_vacuum = 2 ; ", NULL, NULL, NULL); /* --------------------------------------------- * Create table if not exists * --------------------------------------------- */ snprintf(sql, sizeof(sql), "CREATE TABLE IF NOT EXISTS %s (packet BLOB);", TABLE_NAME); if( SQLITE_OK != sqlite3_exec(s_clidb, sql, NULL, NULL, &errmsg) ) { log_error("create table '%s' failed: %s\n", TABLE_NAME, errmsg); sqlite3_free(errmsg); /* free errmsg */ sqlite3_close(s_clidb); /* close databse */ unlink(db_file); /* remove file */ return -3; } log_info("database '%s' initialized ok (table=%s)\n", db_file, TABLE_NAME); return 0; } /* description: close sqlite database handler * return value: none */ void db_close(void) { log_warn("close sqlite database now\n"); sqlite3_close(s_clidb); return ; } /* * description: write a blob packet into database * input args: * pack: blob packet data address * size: blob packet data length * return value: * <0: failure * 0: ok */ int db_write(const void *pack, int size) { static sqlite3_stmt *stmt = NULL; int rv = 0; if (!pack || size <= 0) { log_error("%s(): invalid input arguments\n", __func__); return -1; } if (!s_clidb) { log_error("sqlite database not opened\n"); return -2; } /* Prepare statement once */ if (!stmt) { const char *sql = "INSERT INTO " TABLE_NAME " (packet) VALUES (?);"; if( SQLITE_OK != sqlite3_prepare_v2(s_clidb, sql, -1, &stmt, NULL) ) { log_error("sqlite3_prepare_v2 failed: %s\n", sqlite3_errmsg(s_clidb)); return -3; } } /* Bind blob */ if( SQLITE_OK != sqlite3_bind_blob(stmt, 1, pack, size, SQLITE_STATIC) ) { log_error("sqlite3_bind_blob failed: %s\n", sqlite3_errmsg(s_clidb)); rv = -4; goto out; } /* Execute */ if( SQLITE_DONE != sqlite3_step(stmt) ) { log_error("sqlite3_step failed: %s\n", sqlite3_errmsg(s_clidb)); rv = -5; goto out; } rv = 0; out: sqlite3_clear_bindings(stmt); sqlite3_reset(stmt); if (rv == 0) log_debug("add new blob packet(size=%d) into database ok\n", size); else log_error("add new blob packet(size=%d) into database failed, rv=%d\n", rv); return rv; } /* * description: read the first blob packet and its rowid from database * input args: * pack : blob output buffer * size : buffer size * bytes : actual blob size (output) * rowid : rowid of the record (output) * * return value: * <0 : failure * 0 : ok */ int db_read(void *pack, int size, int *bytes, int *rowid) { static sqlite3_stmt *stmt = NULL; const void *blob_ptr; int rv = 0; if (!pack || size <= 0 || !bytes || !rowid) { log_error("%s(): invalid input arguments\n", __func__); return -1; } if (!s_clidb) { log_error("sqlite database not opened\n"); return -2; } /* Prepare statement once */ if (!stmt) { const char *sql = "SELECT rowid, packet FROM " TABLE_NAME " ORDER BY rowid ASC LIMIT 1;"; if( SQLITE_OK != sqlite3_prepare_v2(s_clidb, sql, -1, &stmt, NULL) ) { log_error("sqlite3_prepare_v2 failed: %s\n", sqlite3_errmsg(s_clidb)); return -3; } } /* Step to first row */ if ( SQLITE_ROW != sqlite3_step(stmt) ) { rv = -4; goto out; } /* get rowid */ *rowid = (int)sqlite3_column_int64(stmt, 0); /* get blob */ blob_ptr = sqlite3_column_blob(stmt, 1); *bytes = sqlite3_column_bytes(stmt, 1); if (!blob_ptr || *bytes <= 0) { log_error("invalid blob data (bytes=%d)\n", *bytes); rv = -5; goto out; } if (*bytes > size) { log_error("blob too large (%d > %d)\n", *bytes, size); rv = -6; goto out; } memcpy(pack, blob_ptr, *bytes); rv = 0; out: sqlite3_reset(stmt); return rv; } /* * description: remove a blob packet by rowid * input args: * rowid : rowid of the record to delete * return value: <0: failure 0:ok */ int db_remove(int rowid) { char sql[SQL_COMMAND_LEN] = {0}; char *errmsg = NULL; int changes = 0; static int count = 0; if (!s_clidb) { log_error("sqlite database not opened\n"); return -1; } if (rowid <= 0) { log_error("invalid rowid (%d)\n", rowid); return -2; } /* Execute SQL */ snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE rowid = %d;", TABLE_NAME, rowid); if (SQLITE_OK != sqlite3_exec(s_clidb, sql, NULL, NULL, &errmsg)) { log_error("remove packet failed (rowid=%d): %s\n", rowid, errmsg ? errmsg : "unknown error"); sqlite3_free(errmsg); return -3; } /* Check how many rows were actually deleted */ changes = sqlite3_changes(s_clidb); if (changes == 0) { log_warn("no record deleted (rowid=%d)\n", rowid); return -4; } log_debug("remove packet ok (rowid=%d)\n", rowid); /* * Optional: vacuum after N successful deletions * WARNING: VACUUM is expensive and blocks DB access */ if (++count >= 500) { log_info("trigger database VACUUM\n"); sqlite3_exec(s_clidb, "VACUUM;", NULL, 0, NULL); count = 0; } return 0; }