335 lines
10 KiB
C++
335 lines
10 KiB
C++
/*
|
|
* Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
|
* You may not use this file except in compliance with the License.
|
|
* A copy of the License is located at
|
|
*
|
|
* http://aws.amazon.com/apache2.0/
|
|
*
|
|
* or in the "license" file accompanying this file. This file is distributed
|
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
|
* express or implied. See the License for the specific language governing
|
|
* permissions and limitations under the License.
|
|
*/
|
|
|
|
#include "SQLiteStorage/SQLiteUtils.h"
|
|
#include "SQLiteStorage/SQLiteStatement.h"
|
|
|
|
#include <AVSCommon/Utils/File/FileUtils.h>
|
|
#include <AVSCommon/Utils/Logger/Logger.h>
|
|
#include <AVSCommon/Utils/String/StringUtils.h>
|
|
|
|
#include <fstream>
|
|
|
|
namespace alexaClientSDK {
|
|
namespace storage {
|
|
namespace sqliteStorage {
|
|
|
|
using namespace avsCommon::utils::file;
|
|
using namespace avsCommon::utils::logger;
|
|
using namespace avsCommon::utils::string;
|
|
|
|
/// String to identify log entries originating from this file.
|
|
static const std::string TAG("SQLiteUtils");
|
|
|
|
/**
|
|
* Create a LogEntry using this file's TAG and the specified event string.
|
|
*
|
|
* @param The event string for this @c LogEntry.
|
|
*/
|
|
#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event)
|
|
|
|
/**
|
|
* A utility function to open or create a SQLite database, depending on the flags being passed in.
|
|
* The possible flags defined by SQLite for this operation are as follows:
|
|
*
|
|
* SQLITE_OPEN_READONLY
|
|
* SQLITE_OPEN_READWRITE
|
|
* SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
|
|
*
|
|
* The meanings of these flags are as one might expect, however for further details please refer to the online
|
|
* documentation here:
|
|
*
|
|
* https://sqlite.org/c3ref/open.html
|
|
*
|
|
* @param filePath The path, including file name, to where the database is to be opened or created.
|
|
* @param sqliteFlags Flags which will be passed to the SQLite call. These flags determine the method of opening.
|
|
* @param[out] dbHandle A double-pointer to a sqlite3 handle which will be updated to point to a valid handle.
|
|
* @return Whether the operation was successful.
|
|
*/
|
|
static sqlite3* openSQLiteDatabaseHelper(const std::string& filePath, int sqliteFlags) {
|
|
sqlite3* dbHandle = nullptr;
|
|
|
|
int rcode = sqlite3_open_v2(
|
|
filePath.c_str(), // file path
|
|
&dbHandle, // the db handle
|
|
sqliteFlags, // flags
|
|
nullptr); // optional vfs name (C-string)
|
|
|
|
if (rcode != SQLITE_OK) {
|
|
ACSDK_ERROR(LX("openSQLiteDatabaseHelperFailed")
|
|
.m("Could not open database.")
|
|
.d("rcode", rcode)
|
|
.d("file path", filePath)
|
|
.d("error message", sqlite3_errmsg(dbHandle)));
|
|
sqlite3_close(dbHandle);
|
|
dbHandle = nullptr;
|
|
}
|
|
|
|
return dbHandle;
|
|
}
|
|
|
|
sqlite3* createSQLiteDatabase(const std::string& filePath) {
|
|
if (fileExists(filePath)) {
|
|
ACSDK_ERROR(LX("createSQLiteDatabaseFailed").m("File already exists.").d("file", filePath));
|
|
return nullptr;
|
|
}
|
|
|
|
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
|
|
sqlite3* dbHandle = openSQLiteDatabaseHelper(filePath, flags);
|
|
|
|
if (!dbHandle) {
|
|
ACSDK_ERROR(LX("createSQLiteDatabaseFailed").m("Could not create database."));
|
|
}
|
|
|
|
return dbHandle;
|
|
}
|
|
|
|
sqlite3* openSQLiteDatabase(const std::string& filePath) {
|
|
if (!fileExists(filePath)) {
|
|
ACSDK_ERROR(LX("openSQLiteDatabaseFailed").m("File could not be found.").d("file", filePath));
|
|
return nullptr;
|
|
}
|
|
|
|
int flags = SQLITE_OPEN_READWRITE;
|
|
sqlite3* dbHandle = openSQLiteDatabaseHelper(filePath, flags);
|
|
|
|
if (!dbHandle) {
|
|
ACSDK_ERROR(LX("openSQLiteDatabaseFailed").m("Could not open database."));
|
|
}
|
|
|
|
return dbHandle;
|
|
}
|
|
|
|
bool closeSQLiteDatabase(sqlite3* dbHandle) {
|
|
if (!dbHandle) {
|
|
ACSDK_ERROR(LX("closeSQLiteDatabaseFailed").m("dbHandle is nullptr."));
|
|
}
|
|
|
|
int rcode = sqlite3_close(dbHandle);
|
|
|
|
if (rcode != SQLITE_OK) {
|
|
ACSDK_ERROR(LX("closeSQLiteDatabaseFailed").d("rcode", rcode).d("error message", sqlite3_errmsg(dbHandle)));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool performQuery(sqlite3* dbHandle, const std::string& sqlString) {
|
|
if (!dbHandle) {
|
|
ACSDK_ERROR(LX("performQueryFailed").m("dbHandle was nullptr."));
|
|
return false;
|
|
}
|
|
|
|
int rcode = sqlite3_exec(
|
|
dbHandle, // handle to the open database
|
|
sqlString.c_str(), // the SQL query
|
|
nullptr, // optional callback function
|
|
nullptr, // first argument to optional callback function
|
|
nullptr); // optional pointer to error message (char**)
|
|
|
|
if (rcode != SQLITE_OK) {
|
|
ACSDK_ERROR(LX("performQueryFailed")
|
|
.m("Could not execute SQL:" + sqlString)
|
|
.d("rcode", rcode)
|
|
.d("error message", sqlite3_errmsg(dbHandle)));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool getNumberTableRows(SQLiteDatabase* db, const std::string& tableName, int* numberRows) {
|
|
if (!db) {
|
|
ACSDK_ERROR(LX("getNumberTableRowsFailed").m("db was nullptr."));
|
|
return false;
|
|
}
|
|
|
|
if (!numberRows) {
|
|
ACSDK_ERROR(LX("getNumberTableRowsFailed").m("numberRows was nullptr."));
|
|
return false;
|
|
}
|
|
|
|
std::string sqlString = "SELECT COUNT(*) FROM " + tableName + ";";
|
|
auto statement = db->createStatement(sqlString);
|
|
|
|
if (!statement) {
|
|
ACSDK_ERROR(LX("getNumberTableRowsFailed").m("Could not create statement."));
|
|
return false;
|
|
}
|
|
|
|
if (!statement->step()) {
|
|
ACSDK_ERROR(LX("getNumberTableRowsFailed").m("Could not step to next row."));
|
|
return false;
|
|
}
|
|
|
|
const int RESULT_COLUMN_POSITION = 0;
|
|
std::string rowValue = statement->getColumnText(RESULT_COLUMN_POSITION);
|
|
|
|
if (!stringToInt(rowValue.c_str(), numberRows)) {
|
|
ACSDK_ERROR(LX("getNumberTableRowsFailed").d("Could not convert string to integer", rowValue));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool getTableMaxIntValue(SQLiteDatabase* db, const std::string& tableName, const std::string& columnName, int* maxInt) {
|
|
if (!db) {
|
|
ACSDK_ERROR(LX("getTableMaxIntValue").m("db was nullptr."));
|
|
return false;
|
|
}
|
|
|
|
if (!maxInt) {
|
|
ACSDK_ERROR(LX("getMaxIntFailed").m("maxInt was nullptr."));
|
|
return false;
|
|
}
|
|
|
|
std::string sqlString =
|
|
"SELECT " + columnName + " FROM " + tableName + " ORDER BY " + columnName + " DESC LIMIT 1;";
|
|
|
|
auto statement = db->createStatement(sqlString);
|
|
|
|
if (!statement) {
|
|
ACSDK_ERROR(LX("getTableMaxIntValueFailed").m("Could not create statement."));
|
|
return false;
|
|
}
|
|
|
|
if (!statement->step()) {
|
|
ACSDK_ERROR(LX("getTableMaxIntValueFailed").m("Could not step to next row."));
|
|
return false;
|
|
}
|
|
|
|
int stepResult = statement->getStepResult();
|
|
|
|
if (stepResult != SQLITE_ROW && stepResult != SQLITE_DONE) {
|
|
ACSDK_ERROR(LX("getTableMaxIntValueFailed").m("Step did not evaluate to either row or completion."));
|
|
return false;
|
|
}
|
|
|
|
// No entries were found in database - set to zero as the current max id.
|
|
if (SQLITE_DONE == stepResult) {
|
|
*maxInt = 0;
|
|
}
|
|
|
|
// Entries were found - let's get the value.
|
|
if (SQLITE_ROW == stepResult) {
|
|
const int RESULT_COLUMN_POSITION = 0;
|
|
std::string rowValue = statement->getColumnText(RESULT_COLUMN_POSITION);
|
|
|
|
if (!stringToInt(rowValue.c_str(), maxInt)) {
|
|
ACSDK_ERROR(LX("getTableMaxIntValueFailed").d("Could not convert string to integer", rowValue));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool tableExists(sqlite3* dbHandle, const std::string& tableName) {
|
|
if (!dbHandle) {
|
|
ACSDK_ERROR(LX("tableExistsFailed").m("dbHandle was nullptr."));
|
|
return false;
|
|
}
|
|
|
|
std::string sqlString = "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='" + tableName + "';";
|
|
|
|
SQLiteStatement statement(dbHandle, sqlString);
|
|
|
|
if (!statement.isValid()) {
|
|
ACSDK_ERROR(LX("tableExistsFailed").m("Could not create statement."));
|
|
return false;
|
|
}
|
|
|
|
if (!statement.step()) {
|
|
ACSDK_ERROR(LX("tableExistsFailed").m("Could not step to next row."));
|
|
return false;
|
|
}
|
|
|
|
int stepResult = statement.getStepResult();
|
|
|
|
if (stepResult != SQLITE_ROW && stepResult != SQLITE_DONE) {
|
|
ACSDK_ERROR(LX("tableExistsFailed").m("Step did not evaluate to either row or completion."));
|
|
return false;
|
|
}
|
|
|
|
// No entries were found in database - the table does not exist.
|
|
if (SQLITE_DONE == stepResult) {
|
|
return false;
|
|
}
|
|
|
|
// Entries were found - let's get the value.
|
|
const int RESULT_COLUMN_POSITION = 0;
|
|
std::string rowValue = statement.getColumnText(RESULT_COLUMN_POSITION);
|
|
|
|
int count = 0;
|
|
if (!stringToInt(rowValue.c_str(), &count)) {
|
|
ACSDK_ERROR(LX("tableExistsFailed").d("Could not convert string to integer", rowValue));
|
|
return false;
|
|
}
|
|
|
|
return (1 == count);
|
|
}
|
|
|
|
bool clearTable(sqlite3* dbHandle, const std::string& tableName) {
|
|
if (!dbHandle) {
|
|
ACSDK_ERROR(LX("clearTableFailed").m("dbHandle was nullptr."));
|
|
return false;
|
|
}
|
|
|
|
std::string sqlString = "DELETE FROM " + tableName + ";";
|
|
|
|
SQLiteStatement statement(dbHandle, sqlString);
|
|
|
|
if (!statement.isValid()) {
|
|
ACSDK_ERROR(LX("clearTableFailed").m("Could not create statement."));
|
|
return false;
|
|
}
|
|
|
|
if (!statement.step()) {
|
|
ACSDK_ERROR(LX("clearTableFailed").m("Could not perform step."));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool dropTable(sqlite3* dbHandle, const std::string& tableName) {
|
|
if (!dbHandle) {
|
|
ACSDK_ERROR(LX("dropTableFailed").m("dbHandle was nullptr."));
|
|
return false;
|
|
}
|
|
|
|
std::string sqlString = "DROP TABLE IF EXISTS " + tableName + ";";
|
|
|
|
SQLiteStatement statement(dbHandle, sqlString);
|
|
|
|
if (!statement.isValid()) {
|
|
ACSDK_ERROR(LX("dropTableFailed").m("Could not create statement."));
|
|
return false;
|
|
}
|
|
|
|
if (!statement.step()) {
|
|
ACSDK_ERROR(LX("dropTableFailed").m("Could not perform step."));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace sqliteStorage
|
|
} // namespace storage
|
|
} // namespace alexaClientSDK
|