Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,77 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SQLiteBoundColumnSet.h"
#include "AzCore/std/containers/bitset.h"
namespace AzToolsFramework
{
namespace SQLite
{
namespace Internal
{
template<>
const void* GetColumnValue(SQLite::Statement* statement, int index)
{
return statement->GetColumnBlob(index);
}
template<>
AZ::u64 GetColumnValue(SQLite::Statement* statement, int index)
{
return statement->GetColumnInt64(index);
}
template<>
AZ::s64 GetColumnValue(SQLite::Statement* statement, int index)
{
return statement->GetColumnInt64(index);
}
template<>
AZ::u32 GetColumnValue(SQLite::Statement* statement, int index)
{
return statement->GetColumnInt(index);
}
template<>
AZ::s32 GetColumnValue(SQLite::Statement* statement, int index)
{
return statement->GetColumnInt(index);
}
template<>
double GetColumnValue(SQLite::Statement* statement, int index)
{
return statement->GetColumnDouble(index);
}
template<>
AZ::Uuid GetColumnValue(SQLite::Statement* statement, int index)
{
return statement->GetColumnUuid(index);
}
template<>
AZStd::string GetColumnValue(SQLite::Statement* statement, int index)
{
return statement->GetColumnText(index);
}
template<>
AZStd::bitset<64> GetColumnValue(SQLite::Statement* statement, int index)
{
return statement->GetColumnInt64(index);
}
} // namespace Internal
} // namespace SQLite
} // namespace AZFramework
@@ -0,0 +1,178 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/std/tuple.h>
#include "SQLiteConnection.h"
namespace AzToolsFramework
{
namespace SQLite
{
namespace Internal
{
// Collection of helper functions to allow calling the appropriate GetColumn function based on the return type ...
template<typename T> T GetColumnValue(SQLite::Statement* statement, int index);
template<typename T, typename AZStd::enable_if_t<AZStd::is_enum<T>::value>* = nullptr>
T GetColumnEnum(SQLite::Statement* statement, int index)
{
// In the case of enums, try to cast to the underlying type and see if we have a GetColumnValue implementation for that type
using EnumType = typename AZStd::underlying_type<T>::type;
return static_cast<T>(GetColumnValue<EnumType>(statement, index));
}
template<typename T, typename AZStd::enable_if_t<!AZStd::is_enum<T>::value>* = nullptr>
T GetColumnEnum(SQLite::Statement* statement, int index)
{
// Non-enum type with no implementation
static_assert(!AZStd::is_same<T, T>::value, "Type not implemented");
}
template<typename T>
T GetColumnValue(SQLite::Statement* statement, int index)
{
// Generic case, if this is an enum type we'll try to cast to the underlying type, otherwise fail
return GetColumnEnum<T>(statement, index);
}
template<>
AZStd::string GetColumnValue(SQLite::Statement* statement, int index);
template<>
AZ::Uuid GetColumnValue(SQLite::Statement* statement, int index);
template<>
double GetColumnValue(SQLite::Statement* statement, int index);
template<>
AZ::s32 GetColumnValue(SQLite::Statement* statement, int index);
template<>
AZ::u32 GetColumnValue(SQLite::Statement* statement, int index);
template<>
AZ::s64 GetColumnValue(SQLite::Statement* statement, int index);
template<>
AZ::u64 GetColumnValue(SQLite::Statement* statement, int index);
template<>
const void* GetColumnValue(SQLite::Statement* statement, int index);
template<>
AZStd::bitset<64> GetColumnValue(SQLite::Statement* statement, int index);
}
// Represents a single column in an SQLite result which is bound to a variable reference where the result will be stored
template<typename T>
struct BoundColumn
{
// columnName = exact match of the name of the column as returned by the SQLite query
// boundMember = reference to the variable where the result will be stored when Fetch is called. This class does not store the data itself
BoundColumn(const char* columnName, T& boundMember) : m_name(columnName), m_boundMember(boundMember) {}
// Retrieves the value stored in the current row for this column and saves the value to the bound member reference
bool Fetch(SQLite::Statement* statement)
{
if (m_index == -1)
{
m_index = statement->FindColumn(m_name);
if (m_index == -1)
{
AZ_Error("AzToolsFramework::SQLite", false, "Failed to find column %s for query", m_name);
return false;
}
}
m_boundMember = Internal::GetColumnValue<T>(statement, m_index);
return true;
}
const char* m_name{};
int m_index{ -1 };
T& m_boundMember;
};
// Helper to allow type-deduction when creating a BoundColumn
template<typename T>
BoundColumn<T> MakeColumn(const char* columnName, T& boundMember)
{
return BoundColumn<T>(columnName, boundMember);
}
// Represents a collection of BoundColumns
// Allows for easily fetching the values of every contained column in a single Fetch call
template<typename... T>
struct BoundColumnSet
{
static constexpr size_t ColumnCount = sizeof...(T);
using ColumnTupleType = AZStd::tuple<BoundColumn<T>...>;
static_assert(ColumnCount > 0, "BoundColumnSet must contain at least one column");
BoundColumnSet(ColumnTupleType columns) : m_columns(columns) {}
// Retrieves the value stored in the current row for every column in the set and saves the values to the bound member references
bool Fetch(SQLite::Statement* statement)
{
return FetchImpl(statement, AZStd::make_index_sequence<ColumnCount>());
}
private:
template<size_t... TIndices>
bool FetchImpl(SQLite::Statement* statement, AZStd::index_sequence<TIndices...>)
{
bool results[] = {AZStd::get<TIndices>(m_columns).Fetch(statement)...};
// Check if any of the Fetch results were false
for(bool result : results)
{
if(!result)
{
return false;
}
}
return true;
}
public:
ColumnTupleType m_columns;
};
// Helper to allow type-deduction when creating a BoundColumnSet
template<typename... T>
BoundColumnSet<T...> MakeColumns(BoundColumn<T>... cols)
{
return BoundColumnSet<T...>(AZStd::make_tuple(AZStd::forward<BoundColumn<T>>(cols)...));
}
// Combines multiple BoundColumnSets into a single BoundColumnSet object
template<typename... T>
auto CombineColumns(T... columnSets)
{
return MakeColumnsFromTuple(AZStd::tuple_cat(columnSets.m_columns...));
}
// Helper to allow type-deduction when creating a BoundColumnSet from a tuple
template<typename... T>
auto MakeColumnsFromTuple(AZStd::tuple<BoundColumn<T>...> tuple)
{
return BoundColumnSet<T...>(AZStd::move(tuple));
}
} // namespace SQLite
} // namespace AZFramework
@@ -0,0 +1,785 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SQLiteConnection.h"
// note that this includes the 3rd Party sqlite implementation.
// if we need to add compile switches, we would add them here.
#include <AzCore/std/parallel/lock.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/std/functional.h>
#include <sqlite3.h>
namespace AzToolsFramework
{
namespace SQLite
{
/** A statement prototype represents a registered statement ("SELECT * FROM assets WHERE assets.name = :name")
* To actually execute it, you'd call GetStatement on the manager which will create for you a Statement from a prototype
*/
class StatementPrototype
{
public:
AZ_CLASS_ALLOCATOR(StatementPrototype, AZ::SystemAllocator, 0)
StatementPrototype();
StatementPrototype(const AZStd::string& stmt);
~StatementPrototype();
void SetSqlText(const AZStd::string& txt) { m_sqlText = txt; }
AZStd::string GetSqlText() { return m_sqlText; }
Statement* Prepare(sqlite3* db); // get a copy of a statement ready to execute
void RetireStatement(Statement* finishedWithStatement); // finished with a statement, return it to pool
private:
AZStd::string m_sqlText;
AZStd::vector<Statement*> m_cachedPreparedStatements;
AZStd::recursive_mutex m_mutex;
sqlite3* m_db;
StatementPrototype(const StatementPrototype& other); // forbid ordinary copy construct
StatementPrototype& operator=(const StatementPrototype& other); // forbid operator=
};
Connection::Connection(void)
: m_db(NULL)
{
}
Connection::~Connection(void)
{
Close();
}
bool Connection::IsOpen() const
{
return (m_db != NULL);
}
bool Connection::Open(const AZStd::string& filename, bool readOnly)
{
AZ_Assert(m_db == NULL, "You have to close the database prior to opening a new one.");
if (m_db)
{
return false;
}
int res = 0;
if (readOnly)
{
res = sqlite3_open_v2(filename.c_str(), &m_db, SQLITE_OPEN_READONLY, nullptr);
}
else
{
res = sqlite3_open(filename.c_str(), &m_db);
}
if ((res != SQLITE_OK)||(!m_db))
{
AZ_Error("SQLiteConnection", false, "Unable to open sql database at %s", filename.c_str());
return false;
}
sqlite3_exec(m_db, "PRAGMA foreign_keys = ON;", NULL, NULL, NULL);
//WAL journal mode enabled for better concurrency with external asset browser.
//Reads do not block writes
sqlite3_exec(m_db, "PRAGMA journal_mode = wal;", NULL, NULL, NULL); // we'll journal using WAL strategy
sqlite3_exec(m_db, "PRAGMA cache_size = 160000;", NULL, NULL, NULL);
// turn sync off - you will lose data on power loss but all the data can be rebuilt from cache anyway.
// you still don't lose data if the application crashes, only if you literally lose power while the disk is writing.
// and because you're in WAL mode, you only lose the current transaction anyway.
sqlite3_exec(m_db, "PRAGMA synchronous = 0;", NULL, NULL, NULL);
return (res == SQLITE_OK);
}
void Connection::Close()
{
if (m_db)
{
FinalizeAll();
sqlite3_close(m_db);
m_db = NULL;
}
}
void Connection::FinalizeAll()
{
for (auto it : m_statementPrototypes)
{
delete it.second;
}
m_statementPrototypes.clear();
}
void Connection::AddStatement(const AZStd::string& shortName, const AZStd::string& sqlText)
{
if (m_statementPrototypes.find(shortName) != m_statementPrototypes.end())
{
AZ_Assert(false, "You may not register the same prototype twice. Attempted to register %s twice!", shortName.c_str());
return;
}
m_statementPrototypes[shortName] = aznew StatementPrototype(sqlText);//, AZStd::move(StatementPrototype(sqlText))));
}
void Connection::RemoveStatement(const char* name)
{
auto it = m_statementPrototypes.find(name);
if (it == m_statementPrototypes.end())
{
AZ_Assert(false, "Asked to remove a statement: %s : which does not currently exist\n", name);
return;
}
delete it->second;
m_statementPrototypes.erase(it);
}
void Connection::AddStatement(const char* shortName, const char* sqlText)
{
AddStatement(AZStd::string(shortName), AZStd::string(sqlText));
}
Statement* Connection::GetStatement(const AZStd::string& stmtName)
{
auto item = m_statementPrototypes.find(stmtName);
if (item == m_statementPrototypes.end())
{
AZ_Assert(false, "Invalid statement requested from the sql connection '%s'", stmtName.c_str());
return nullptr;
}
return item->second->Prepare(m_db);
}
Statement* StatementPrototype::Prepare(sqlite3* db)
{
{
AZStd::lock_guard<AZStd::recursive_mutex> myLocker(m_mutex);
if (!m_cachedPreparedStatements.empty())
{
Statement* prePrepared = m_cachedPreparedStatements.back();
m_cachedPreparedStatements.pop_back();
return prePrepared;
}
}
// if we get here, we have no such prepared statement
Statement* newStatement = aznew Statement(this);
newStatement->PrepareFirstTime(db);
return newStatement;
}
void StatementPrototype::RetireStatement(Statement* finishedWithStatement)
{
AZ_Assert(finishedWithStatement, "null statement");
if (!finishedWithStatement)
{
return;
}
AZ_Assert(finishedWithStatement->GetParentPrototype() == this, "Invalid call to retire a statement to wrong parent.");
if (finishedWithStatement->GetParentPrototype() != this)
{
return;
}
finishedWithStatement->Reset();
if (finishedWithStatement->Prepared())
{
// we only want to cache valid statements (that didn't fail to initialize) for later.
AZStd::lock_guard<AZStd::recursive_mutex> myLock(m_mutex);
m_cachedPreparedStatements.push_back(finishedWithStatement);
}
else
{
// delete invalid statement!
delete finishedWithStatement;
}
}
void Connection::BeginTransaction()
{
AZ_Assert(m_db, "BeginTransaction: Database is not open!");
if (!m_db)
{
return;
}
sqlite3_exec(m_db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
}
void Connection::CommitTransaction()
{
AZ_Assert(m_db, "CommitTransaction: Database is not open!");
if (!m_db)
{
return;
}
sqlite3_exec(m_db, "COMMIT TRANSACTION;", NULL, NULL, NULL);
}
void Connection::RollbackTransaction()
{
AZ_Assert(m_db, "RollbackTransaction: Database is not open!");
if (!m_db)
{
return;
}
sqlite3_exec(m_db, "ROLLBACK;", NULL, NULL, NULL);
}
void Connection::Vacuum()
{
AZ_Assert(m_db, "Vacuum: Database is not open!");
if (!m_db)
{
return;
}
sqlite3_exec(m_db, "VACUUM;", NULL, NULL, NULL);
}
AZ::s64 Connection::GetLastRowID()
{
AZ_Assert(m_db, "GetLastRowID: Database is not open!");
if (!m_db)
{
return 0;
}
return sqlite3_last_insert_rowid(m_db);
}
int Connection::GetNumAffectedRows()
{
return sqlite3_changes(m_db);
}
bool Connection::ExecuteOneOffStatement(const char* name)
{
AZ_Assert(IsOpen(), "Invalid operation - Database is not open.");
AZ_Assert(name, "Invalid input - name is not valid");
if ((!IsOpen()) || (!name))
{
return false;
}
Statement* pState = GetStatement(name);
if (!pState)
{
return false;
}
int res = pState->Step();
pState->Finalize();
if (res == Statement::SqlError)
{
return false;
}
return true;
}
bool Connection::ExecuteRawSqlQuery(const AZStd::string& sql, const AZStd::function<bool(sqlite3_stmt*)>& resultCallback, const AZStd::function<void(sqlite3_stmt*)>& bindCallback)
{
sqlite3_stmt* statement;
int res = sqlite3_prepare_v2(m_db, sql.c_str(), aznumeric_caster(sql.length() + 1), &statement, nullptr);
if(res != SQLITE_OK)
{
AZ_Error("Sqlite", false, "Failed to prepare statement. Error code %d, sql: %s", sqlite3_extended_errcode(m_db), sql.c_str());
return false;
}
bindCallback(statement);
res = sqlite3_step(statement);
bool validResult = res == SQLITE_DONE;
while(res == SQLITE_ROW)
{
validResult = true;
if (resultCallback && resultCallback(statement))
{
res = sqlite3_step(statement);
}
}
if(res != SQLITE_OK && res != SQLITE_DONE && res != SQLITE_ROW)
{
AZ_Error("Sqlite", false, "Failed to step statement. Error code %d, sql: %s", sqlite3_extended_errcode(m_db), sql.c_str());
}
sqlite3_finalize(statement);
return validResult;
}
bool Connection::DoesTableExist(const char* name)
{
AZ_Assert(IsOpen(), "Connection::DoesTableExist - Invalid state - Database is not open.");
AZ_Assert(name, "Connection::DoesTableExist - Invalid input - name is nullptr");
if ((!IsOpen())||(!name))
{
return false;
}
if (name[0] == 0)
{
AZ_Assert(false, "Connection::DoesTableExist - Invalid input - name is empty string.");
return false;
}
StatementPrototype stmt("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=:1;");
Statement* execute = stmt.Prepare(m_db); // execute now belongs to stmt and will die when stmt leaves scope.
if (!execute->Prepared())
{
execute->Finalize();
return false;
}
execute->BindValueText(1, name);
int res = execute->Step();
if (res == Statement::SqlError)
{
execute->Finalize();
return false;
}
bool hasData = execute->GetColumnInt(0) != 0;
execute->Finalize();
return hasData;
}
AZStd::string GetColumnText(sqlite3_stmt* statement, int col)
{
AZ_Assert(statement, "Statement::GetColumnText: Statement not ready!");
if (!statement)
{
return AZStd::string();
}
const unsigned char* str = sqlite3_column_text(statement, col);
if (str)
{
return reinterpret_cast<const char*>(str);
}
return AZStd::string();
}
int GetColumnInt(sqlite3_stmt* statement, int col)
{
AZ_Assert(statement, "Statement::GetColumnInt: Statement not ready!");
if (!statement)
{
return 0;
}
return sqlite3_column_int(statement, col);
}
AZ::s64 GetColumnInt64(sqlite3_stmt* statement, int col)
{
AZ_Assert(statement, "Statement::GetColumnInt64: Statement not ready!");
if (!statement)
{
return 0;
}
return sqlite3_column_int64(statement, col);
}
double GetColumnDouble(sqlite3_stmt* statement, int col)
{
AZ_Assert(statement, "Statement::GetColumnDouble: Statement not ready!");
if (!statement)
{
return 0.0;
}
return sqlite3_column_double(statement, col);
}
const void* GetColumnBlob(sqlite3_stmt* statement, int col)
{
AZ_Assert(statement, "Statement::GetColumnBlob: Statement not ready!");
if (!statement)
{
return nullptr;
}
return sqlite3_column_blob(statement, col);
}
int GetColumnBlobBytes(sqlite3_stmt* statement, int col)
{
AZ_Assert(statement, "Statement::GetColumnBlobBytes: Statement not ready!");
if (!statement)
{
return 0;
}
return sqlite3_column_bytes(statement, col);
}
AZ::Uuid GetColumnUuid(sqlite3_stmt* statement, int col)
{
const void* blobAddr = GetColumnBlob(statement, col);
int blobBytes = GetColumnBlobBytes(statement, col);
AZ::Uuid newUuid;
AZ_Error("SQLiteConnection", blobAddr && (blobBytes == sizeof(newUuid.data)), "GetColumnUuid: Database column %i does not contain a UUID - could be a sign of a corrupt database.", col);
if ((!blobAddr) || (blobBytes != sizeof(newUuid.data)))
{
return AZ::Uuid::CreateNull();
}
memcpy(newUuid.data, blobAddr, blobBytes);
return newUuid;
}
/////////////////////////////////////////////////
void Statement::Finalize()
{
if (m_parentPrototype)
{
m_parentPrototype->RetireStatement(this);
}
}
Statement::Statement(StatementPrototype* parent)
: m_statement(NULL)
, m_parentPrototype(parent)
{
}
Statement::~Statement()
{
if (m_statement)
{
sqlite3_finalize(m_statement);
}
m_statement = nullptr;
}
StatementPrototype::StatementPrototype(const AZStd::string& sql)
: m_sqlText(sql)
{
}
StatementPrototype::~StatementPrototype()
{
for (auto element : m_cachedPreparedStatements)
{
delete element;
}
}
bool Statement::PrepareFirstTime(sqlite3* db)
{
AZ_Assert(db, "PrepareFirstTime: Database is null!");
// NOTE: length() + 1 because of this statement in the SQLITE documentation on sqlite3_prepare_v2:
// "If the caller knows that the supplied string is null-terminated, then there is a small performance advantage
// to passing an nByte parameter that is the number of bytes in the input string including the null-terminator."
// https://www.sqlite.org/c3ref/prepare.html ^^^^^^^^^
int res = sqlite3_prepare_v2(db, m_parentPrototype->GetSqlText().c_str(), (int)m_parentPrototype->GetSqlText().length() + 1, &m_statement, NULL);
AZ_Error("SQLiteConnection", res == SQLITE_OK, "Statement::PrepareFirstTime: failed! %s ( prototype is '%s'). Error code returned is %d.", sqlite3_errmsg(db), m_parentPrototype->GetSqlText().c_str(), res);
return ((res == SQLITE_OK)&&(m_statement));
}
bool Statement::Prepared() const
{
return (m_statement != NULL);
}
Statement::SqlStatus Statement::Step()
{
AZ_Assert(m_statement, "Statement::Step: Statement not ready!");
if (!m_statement)
{
return SqlError;
}
int res = SQLITE_BUSY;
while (res == SQLITE_BUSY)
{
res = sqlite3_step(m_statement);
}
// These 3 result codes are the ONLY non-error result codes for the v2 interface according to sqlite documentation, any other return value is an error.
AZ_Error("SQLiteConnection", res == SQLITE_OK || res == SQLITE_ROW || res == SQLITE_DONE, "Statement::Step() resulted in error code %d. This could indicate a problem with the asset database in the cache.", res);
if (res == SQLITE_ROW)
{
return SqlOK;
}
else if (res == SQLITE_DONE)
{
return SqlDone;
}
return SqlError;
}
int Statement::FindColumn(const char* name)
{
AZ_Assert(m_statement, "Statement::FindColumn: Statement not ready!");
if (!m_statement)
{
return -1;
}
if (!m_cachedColumnNames.empty())
{
auto it = m_cachedColumnNames.find(name);
if (it == m_cachedColumnNames.end())
{
return -1;
}
return it->second;
}
// build the cache:
int columnCount = sqlite3_column_count(m_statement);
if (columnCount == 0)
{
return -1;
}
for (int idx = 0; idx < columnCount; ++idx)
{
m_cachedColumnNames[sqlite3_column_name(m_statement, idx)] = idx;
}
return FindColumn(name);
}
AZStd::string Statement::GetColumnText(int col)
{
AZ_Assert(m_statement, "Statement::GetColumnText: Statement not ready!");
if (!m_statement)
{
return AZStd::string();
}
const unsigned char* str = sqlite3_column_text(m_statement, col);
if (str)
{
return reinterpret_cast<const char*>(str);
}
return AZStd::string();
}
int Statement::GetColumnInt(int col)
{
return SQLite::GetColumnInt(m_statement, col);
}
AZ::s64 Statement::GetColumnInt64(int col)
{
return SQLite::GetColumnInt64(m_statement, col);
}
double Statement::GetColumnDouble(int col)
{
return SQLite::GetColumnDouble(m_statement, col);
}
int Statement::GetColumnBlobBytes(int col)
{
return SQLite::GetColumnBlobBytes(m_statement, col);
}
const void* Statement::GetColumnBlob(int col)
{
return SQLite::GetColumnBlob(m_statement, col);
}
AZ::Uuid Statement::GetColumnUuid(int col)
{
return SQLite::GetColumnUuid(m_statement, col);
}
bool Statement::BindValueBlob(int idx, void* data, int size)
{
AZ_Assert(m_statement, "Statement::GetColumnBlob: Statement not ready!");
if (!m_statement)
{
return false;
}
int res = sqlite3_bind_blob(m_statement, idx, data, size, nullptr);
AZ_Assert(res == SQLITE_OK, "Statement::BindValueBlob: failed to bind!");
return (res == SQLITE_OK);
}
bool Statement::BindValueUuid(int idx, const AZ::Uuid& data)
{
AZ_Assert(m_statement, "Statement::BindValueUuid: Statement not ready!");
if (!m_statement)
{
return false;
}
int res = sqlite3_bind_blob(m_statement, idx, data.data, sizeof(data.data), nullptr);
AZ_Assert(res == SQLITE_OK, "Statement::BindValueUuid: failed to bind!");
return (res == SQLITE_OK);
}
bool Statement::BindValueDouble(int idx, double data)
{
AZ_Assert(m_statement, "Statement::BindValueDouble: Statement not ready!");
if (!m_statement)
{
return false;
}
int res = sqlite3_bind_double(m_statement, idx, data);
AZ_Assert(res == SQLITE_OK, "Statement::BindValueDouble: failed to bind!");
return (res == SQLITE_OK);
}
bool Statement::BindValueInt(int idx, int data)
{
AZ_Assert(m_statement, "Statement::BindValueInt: Statement not ready!");
if (!m_statement)
{
return false;
}
int res = sqlite3_bind_int(m_statement, idx, data);
AZ_Assert(res == SQLITE_OK, "Statement::BindValueInt: failed to bind!");
return (res == SQLITE_OK);
}
bool Statement::BindValueInt64(int idx, AZ::s64 data)
{
AZ_Assert(m_statement, "Statement::BindValueInt64: Statement not ready!");
if (!m_statement)
{
return false;
}
int res = sqlite3_bind_int64(m_statement, idx, data);
AZ_Assert(res == SQLITE_OK, "Statement::BindValueInt64: failed to bind!");
return (res == SQLITE_OK);
}
bool Statement::BindValueText(int idx, const char* data)
{
AZ_Assert(m_statement, "Statement::BindValueText: Statement not ready!");
if (!m_statement)
{
return false;
}
int res = sqlite3_bind_text(m_statement, idx, data, static_cast<int>(strlen(data)), NULL);
AZ_Assert(res == SQLITE_OK, "Statement::BindValueText: failed to bind!");
return (res == SQLITE_OK);
}
bool Statement::Reset()
{
if (!m_statement)
{
return false; // no sqlite3 resources to clean up. this is NOT AN ASSERT situation
}
sqlite3_reset(m_statement);
int res = sqlite3_clear_bindings(m_statement);
AZ_Assert(res == SQLITE_OK, "Statement::sqlite3_clear_bindings: failed!");
return (res == SQLITE_OK);
}
int Statement::GetNamedParamIdx(const char* name)
{
AZ_Assert(m_statement, "Statement::GetNamedParamIdx: Statement not ready!");
if (m_statement)
{
int returnVal = sqlite3_bind_parameter_index(m_statement, name);
AZ_Assert(returnVal, "Parameter %s not found in statement %s!", name, m_parentPrototype->GetSqlText().c_str());
return returnVal;
}
return 0; // named params actually start at 1 - so zero is an ok error value.
}
const StatementPrototype* Statement::GetParentPrototype() const
{
return m_parentPrototype;
}
StatementAutoFinalizer::StatementAutoFinalizer(Connection& connect, const char* statementName)
{
m_statement = connect.GetStatement(statementName);
}
StatementAutoFinalizer::StatementAutoFinalizer(StatementAutoFinalizer&& other)
: m_statement(AZStd::move(other.m_statement))
{
other.m_statement = nullptr;
}
StatementAutoFinalizer& StatementAutoFinalizer::operator=(StatementAutoFinalizer&& other)
{
if (this != &other)
{
m_statement = AZStd::move(other.m_statement);
other.m_statement = nullptr;
}
return *this;
}
StatementAutoFinalizer::~StatementAutoFinalizer()
{
if (m_statement)
{
m_statement->Finalize();
m_statement = nullptr;
}
}
Statement* StatementAutoFinalizer::Get() const
{
return m_statement;
}
ScopedTransaction::ScopedTransaction(Connection* connect)
{
m_connection = connect;
m_connection->BeginTransaction();
}
ScopedTransaction::~ScopedTransaction()
{
if (m_connection)
{
m_connection->RollbackTransaction();
m_connection = nullptr;
}
}
void ScopedTransaction::Commit()
{
if (m_connection)
{
m_connection->CommitTransaction();
m_connection = nullptr;
}
}
} // namespace SQLite
} // namespace AzFramework
@@ -0,0 +1,205 @@
#ifndef AZFRAMEWORK_SQLITECONNECTION_H
#define AZFRAMEWORK_SQLITECONNECTION_H
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/base.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/parallel/mutex.h>
typedef struct sqlite3 sqlite3;
typedef struct sqlite3_stmt sqlite3_stmt;
namespace AZ
{
struct Uuid;
}
namespace AzToolsFramework
{
namespace SQLite
{
class StatementPrototype;
class Statement;
/** AzToolsFramework::SQLite::Connection represents a barebones, single-threaded connection to a SQLite database
*/
class Connection
{
public:
AZ_CLASS_ALLOCATOR(Connection, AZ::SystemAllocator, 0)
Connection(void);
~Connection(void);
//! Open a database connection given a filename
bool Open(const AZStd::string& filename, bool readOnly);
void Close();
bool IsOpen() const;
// ----- Transaction support -----
void BeginTransaction();
void CommitTransaction();
void RollbackTransaction();
// -------------------------------
//! SQLite-specific, compacts the database and cleans up any temporary space allocated.
void Vacuum();
//! Registers a prepared statement with the database
void AddStatement(const AZStd::string& shortName, const AZStd::string& sqlStatement);
void AddStatement(const char* shortName, const char* sqlStatement);
//! Looks up a prepared statement and returns a Statement handle to it, which can then be used
//! To bind parameters and execute the statement.
Statement* GetStatement(const AZStd::string& stmtName);
//! Unregisters and finalizes the statement, freeing its memory
void RemoveStatement(const char* name);
//! Removes all statements and frees their memory.
void FinalizeAll();
//! Returns the ID of the last row inserted by an INSERT query
//! This value is unaffected by other types of queries.
AZ::s64 GetLastRowID();
//! Returns the number of rows affected by the most recent statement.
int GetNumAffectedRows();
//! If a Statement takes no parameters, you can execute it one-off without binding any parameters:
bool ExecuteOneOffStatement(const char* name);
//! Prepares and executes an sql string, running callback on each result row. If callback returns false, iteration will stop and the function will exit
//! bindCallback is called after the query is prepared and gives an option to bind any needed parameters
bool ExecuteRawSqlQuery(const AZStd::string& sql, const AZStd::function<bool(sqlite3_stmt*)>& resultCallback, const AZStd::function<void(sqlite3_stmt*)>& bindCallback);
//! Returns true if the given table name exists in the database.
bool DoesTableExist(const char* name);
private:
sqlite3* m_db;
typedef AZStd::unordered_map< AZStd::string, StatementPrototype* > StatementContainer;
StatementContainer m_statementPrototypes;
};
AZStd::string GetColumnText(sqlite3_stmt* statement, int col);
int GetColumnInt(sqlite3_stmt* statement, int col);
double GetColumnDouble(sqlite3_stmt* statement, int col);
const void* GetColumnBlob(sqlite3_stmt* statement, int col);
int GetColumnBlobBytes(sqlite3_stmt* statement, int col);
AZ::s64 GetColumnInt64(sqlite3_stmt* statement, int col);
AZ::Uuid GetColumnUuid(sqlite3_stmt* statement, int col);
/** A statement is a live, working-right now statement (or a cached one) which you are currently executing.
* All binding is by reference, so you must not destroy the bound objects before executing the statement.
*/
class Statement
{
public:
AZ_CLASS_ALLOCATOR(Statement, AZ::SystemAllocator, 0);
Statement(StatementPrototype* parent);
~Statement();
enum SqlStatus
{
SqlError = 0,
SqlOK,
SqlDone
};
SqlStatus Step();
void Finalize();
bool Prepared() const;
bool PrepareFirstTime(sqlite3* db);
bool Reset();
// only valid during step()
int FindColumn(const char* name);
AZStd::string GetColumnText(int col);
int GetColumnInt(int col);
double GetColumnDouble(int col);
const void* GetColumnBlob(int col);
int GetColumnBlobBytes(int col);
AZ::s64 GetColumnInt64(int col);
AZ::Uuid GetColumnUuid(int col);
bool BindValueUuid(int col, const AZ::Uuid& data);
bool BindValueBlob(int col, void* data, int dataSize);
bool BindValueDouble(int col, double data);
bool BindValueInt(int col, int data);
bool BindValueText(int idx, const char* data);
bool BindValueInt64(int idx, AZ::s64 data);
//! returns zero if it does not find the named index.
int GetNamedParamIdx(const char* name);
// internal use only
const StatementPrototype* GetParentPrototype() const;
private:
sqlite3_stmt* m_statement;
AZStd::unordered_map<AZStd::string, int> m_cachedColumnNames;
StatementPrototype* m_parentPrototype;
// no copy, no default construct allowed, no assignment, etc
Statement(const Statement& other) = delete;
Statement(const Statement&& other) = delete;
Statement() = delete;
};
// a utility class which auto-finalizes a statement in a scope.
// use Get() to retrieve the statement. it will be null if it couldn't find it.
// The auto finalizer owns the statement
class StatementAutoFinalizer
{
public:
StatementAutoFinalizer() = default;
StatementAutoFinalizer(Connection& connect, const char* statementName);
StatementAutoFinalizer(const StatementAutoFinalizer&) = delete;
StatementAutoFinalizer& operator=(const StatementAutoFinalizer&) = delete;
StatementAutoFinalizer(StatementAutoFinalizer&& other);
StatementAutoFinalizer& operator=(StatementAutoFinalizer&& other);
~StatementAutoFinalizer();
Statement* Get() const;
private:
Statement* m_statement = nullptr;
};
//! A utility class to limit a transaction by scope
//! unless you tell it to commit the transaction it will revert automatically
//! if scope is lost for any reason
class ScopedTransaction
{
public:
ScopedTransaction(Connection* connect);
~ScopedTransaction();
void Commit();
// no default construction allowed neither is copy:
ScopedTransaction(const ScopedTransaction& other) = delete;
ScopedTransaction(ScopedTransaction&& other) = delete;
ScopedTransaction& operator=(const ScopedTransaction& other) = delete;
private:
Connection* m_connection = nullptr;
};
} // namespace SQLite
} // namespace AzFramework
#endif //AZFRAMEWORK_SQLITECONNECTION_H
@@ -0,0 +1,83 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SQLiteQuery.h"
#include <AzToolsFramework/SQLite/SQLiteQueryLogBus.h>
namespace AzToolsFramework
{
namespace SQLite
{
namespace Internal
{
void LogQuery(const char* statement, const AZStd::string& params)
{
SQLiteQueryLogBus::Broadcast(&SQLiteQueryLogBus::Events::LogQuery, statement, params);
}
void LogResultId(AZ::s64 rowId)
{
SQLiteQueryLogBus::Broadcast(&SQLiteQueryLogBus::Events::LogResultId, rowId);
}
bool Bind(Statement* statement, int index, const AZ::Uuid& value)
{
return statement->BindValueUuid(index, value);
}
bool Bind(Statement* statement, int index, double value)
{
return statement->BindValueDouble(index, value);
}
bool Bind(Statement* statement, int index, AZ::s32 value)
{
return statement->BindValueInt(index, value);
}
bool Bind(Statement* statement, int index, AZ::u32 value)
{
return statement->BindValueInt(index, value);
}
bool Bind(Statement* statement, int index, const char* value)
{
return statement->BindValueText(index, value);
}
bool Bind(Statement* statement, int index, AZ::s64 value)
{
return statement->BindValueInt64(index, value);
}
bool Bind(Statement* statement, int index, AZ::u64 value)
{
return statement->BindValueInt64(index, value);
}
bool Bind(Statement* statement, int index, const SqlBlob& value)
{
return statement->BindValueBlob(index, value.m_data, value.m_dataSize);
}
} // namespace Internal
} // namespace SQLite
} // namespace AZFramework
std::ostream& std::operator<<(ostream& out, const AZ::Uuid& uuid)
{
return out << uuid.ToString<AZStd::string>().c_str();
}
std::ostream& std::operator<<(ostream& out, const AzToolsFramework::SQLite::SqlBlob&)
{
return out << "[Blob]";
}
@@ -0,0 +1,273 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include "SQLiteConnection.h"
#include <tuple>
#include <sstream>
namespace AzToolsFramework
{
namespace SQLite
{
struct SqlBlob;
}
}
namespace std
{
ostream& operator<<(ostream& out, const AZ::Uuid& uuid);
ostream& operator<<(ostream& out, const AzToolsFramework::SQLite::SqlBlob&);
}
namespace AzToolsFramework
{
namespace SQLite
{
//! Represents a binary data blob. Needed so that Bind can accept a pointer and size as a single type
struct SqlBlob
{
void* m_data;
int m_dataSize;
};
//! Represents a single query parameter, where T is the type of the field
template<typename T>
struct SqlParam
{
explicit SqlParam(const char* parameterName) : m_parameterName(parameterName) {}
const char* m_parameterName;
};
//////////////////////////////////////////////////////////////////////////
namespace Internal
{
void LogQuery(const char* statement, const AZStd::string& params);
void LogResultId(AZ::s64 rowId);
bool Bind(Statement* statement, int index, const AZ::Uuid& value);
bool Bind(Statement* statement, int index, double value);
bool Bind(Statement* statement, int index, AZ::s32 value);
bool Bind(Statement* statement, int index, AZ::u32 value);
bool Bind(Statement* statement, int index, const char* value);
bool Bind(Statement* statement, int index, AZ::s64 value);
bool Bind(Statement* statement, int index, AZ::u64 value);
bool Bind(Statement* statement, int index, const SqlBlob& value);
}
//! Helper object used to provide a query callback that needs to accept multiple arguments
//! This is just a slightly easier-to-use/less verbose (at the callsite) alternative to AZStd::bind'ing the arguments to the callback
template<typename T>
struct SqlQueryResultRunner
{
using HandlerFunc = AZStd::function<bool(T&)>;
SqlQueryResultRunner(bool bindSucceeded, const HandlerFunc& handler, const char* statementName, StatementAutoFinalizer autoFinalizer) :
m_bindSucceeded(bindSucceeded),
m_handler(handler),
m_statementName(statementName),
m_autoFinalizer(AZStd::move(autoFinalizer))
{
}
template<typename TCallback, typename... TArgs>
bool Query(const TCallback& callback, TArgs... args)
{
if (m_bindSucceeded)
{
return callback(m_statementName, m_autoFinalizer.Get(), m_handler, args...);
}
return false;
}
const char* m_statementName;
StatementAutoFinalizer m_autoFinalizer;
const HandlerFunc& m_handler;
bool m_bindSucceeded;
};
template<typename... T>
class SqlQuery
{
public:
SqlQuery(const char* statementName, const char* statement, const char* logName, SqlParam<T>&&... parameters) :
m_statementName(statementName),
m_statement(statement),
m_logName(logName),
m_parameters(parameters...)
{
}
//! Bind both prepares and binds the args - call it on an empty autoFinalizer and it will prepare
//! the query for you and return a ready-to-go autoFinalizer that has a valid statement ready to
//! step()
bool Bind(Connection& connection, StatementAutoFinalizer& autoFinalizer, const T&... args) const
{
// bind is meant to prepare the auto finalizer and prepare the connection, so assert if the
// programmer has accidentally already bound it or prepared it first.
AZ_Assert(!autoFinalizer.Get(), "Do not call Bind() on an autofinalizer that is already attached to a connection.");
autoFinalizer = StatementAutoFinalizer(connection, m_statementName);
Statement* statement = autoFinalizer.Get();
if (statement == nullptr)
{
AZ_Error(m_logName, false, "Could not find statement %s", m_statementName);
return false;
}
bool result = BindInternal<0>(statement, args...);
AZStd::string debugParams;
ArgsToString<0>(debugParams, args...);
Internal::LogQuery(m_statement, debugParams);
return result;
}
//! BindAndStep will execute the given statement and then clean up afterwards. It is for
//! calls that perform some operation on the database rather than a query that you need the result of.
bool BindAndStep(Connection& connection, const T&... args) const
{
StatementAutoFinalizer autoFinalizer;
if (!Bind(connection, autoFinalizer, args...))
{
return false;
}
if (autoFinalizer.Get()->Step() == Statement::SqlError)
{
AZStd::string argString;
ArgsToString<0>(argString, args...);
AZ_Warning(m_logName, false, "Failed to execute statement %s.\nQuery: %s\nParams: %s", m_statementName, m_statement, argString.c_str());
return false;
}
Internal::LogResultId(connection.GetLastRowID());
return true;
}
//! Similar to Bind, this will prepare and bind the args. Additionally, it will then call the callback with (statementName, statement, handler)
//! The statement will be finalized automatically as part of this call
template<typename THandler, typename TCallback>
bool BindAndQuery(Connection& connection, THandler&& handler, const TCallback& callback, const T&... args) const
{
StatementAutoFinalizer autoFinal;
if (!Bind(connection, autoFinal, args...))
{
return false;
}
return callback(m_statementName, autoFinal.Get(), AZStd::forward<THandler>(handler));
}
//! Similar to Bind, this will prepare and bind the args.
//! Returns a ResultRunner object that can be passed a callback and any number of arguments to forward to the callback *in addition* to the already supplied (statementName, statement, handler) arguments
//! The statement will be finalized automatically when the ResultRunner goes out of scope
template<typename TResultEntry>
SqlQueryResultRunner<TResultEntry> BindAndThen(Connection& connection, const AZStd::function<bool(TResultEntry&)>& handler, const T&... args) const
{
StatementAutoFinalizer autoFinal;
bool result = Bind(connection, autoFinal, args...);
return SqlQueryResultRunner<TResultEntry>(result, handler, m_statementName, AZStd::move(autoFinal));
}
private:
// Handles the 0-parameter case
template<int TIndex>
static bool BindInternal(Statement*)
{
return true;
}
template<int TIndex, typename T2>
bool BindInternal(Statement* statement, const T2& value) const
{
const SqlParam<T2>& sqlParam = std::get<TIndex>(m_parameters);
int index = statement->GetNamedParamIdx(sqlParam.m_parameterName);
if (!index)
{
AZ_Error(m_logName, false, "Could not find the index for placeholder %s in statement %s ", sqlParam.m_parameterName, m_statementName);
return false;
}
Internal::Bind(statement, index, value);
return true;
}
template<int TIndex, typename T2, typename... TArgs>
bool BindInternal(Statement* statement, const T2& value, const TArgs&... args) const
{
return BindInternal<TIndex>(statement, value)
&& BindInternal<TIndex + 1>(statement, args...);
}
template<int TIndex, typename TArg, typename...TArgs>
void ArgsToString(AZStd::string& argsStringOutput, const TArg& arg, const TArgs&... args) const
{
const SqlParam<TArg>& sqlParam = std::get<TIndex>(m_parameters);
std::ostringstream paramStringStream;
if(!argsStringOutput.empty())
{
paramStringStream << ", ";
}
paramStringStream << sqlParam.m_parameterName << " = `" << arg << "`";
argsStringOutput.append(paramStringStream.str().c_str());
ArgsToString<TIndex + 1>(argsStringOutput, args...);
}
// Handles 0-parameter case
template<int TIndex>
static void ArgsToString(AZStd::string&)
{
}
public:
const char* m_statementName;
const char* m_statement;
const char* m_logName;
std::tuple<SqlParam<T>...> m_parameters;
};
template<typename TSqlQuery>
void AddStatement(Connection* connection, const TSqlQuery& sqlQuery)
{
connection->AddStatement(sqlQuery.m_statementName, sqlQuery.m_statement);
}
template<typename... T>
SqlQuery<T...> MakeSqlQuery(const char* statementName, const char* statement, const char* logName, SqlParam<T>&&... parameters)
{
return SqlQuery<T...>(statementName, statement, logName, AZStd::forward<SqlParam<T>>(parameters)...);
}
} // namespace SQLite
} // namespace AZFramework
@@ -0,0 +1,33 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
namespace AzToolsFramework
{
namespace SQLite
{
class SQLiteQueryLogEvents
: public AZ::EBusTraits
{
public:
using MutexType = AZStd::recursive_mutex;
virtual void LogQuery(const char* statement, const AZStd::string& params) = 0;
virtual void LogResultId(AZ::s64 rowId) = 0;
};
typedef AZ::EBus<SQLiteQueryLogEvents> SQLiteQueryLogBus;
} // namespace SQLite
} // namespace AzToolsFramework