#include "Statement.h" #include "Database.h" #include "NativeBuffer.h" #include #include #include #include #include using namespace VBA10::SQLiteWinRT; using namespace Platform; using namespace Windows::Storage::Streams; using namespace Windows::Foundation; using namespace concurrency; using namespace Microsoft::WRL; std::unique_ptr VBA10::SQLiteWinRT::PlatformStringToCharArray(Platform::String^ string) { //copy from msdn, need this to get correct buffer size https://social.msdn.microsoft.com/forums/windowsapps/en-US/ce2ecbe8-f009-41c0-bbf4-e6f20eca9eb5/winrt-to-win32-string-converstions auto requiredBufferSize = WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, string->Data(), static_cast(string->Length()), nullptr, 0, nullptr, nullptr ); requiredBufferSize++; auto wideData = string->Data(); std::unique_ptr ansi(new char[requiredBufferSize]); if (0 == WideCharToMultiByte(CP_UTF8, 0, wideData, -1, ansi.get(), requiredBufferSize, NULL, NULL)) throw ref new FailureException(L"Can't convert string to UTF8"); return ansi; } void VBA10::SQLiteWinRT::WasteTimeToSimulateSlowOperation() { auto handle = GetCurrentThread(); WaitForSingleObjectEx(handle, WASTE_TIME_MS, FALSE); } Streams::IBuffer ^VBA10::SQLiteWinRT::CreateNativeBuffer(LPVOID lpBuffer, DWORD nNumberOfBytes) { Microsoft::WRL::ComPtr nativeBuffer; Microsoft::WRL::Details::MakeAndInitialize(&nativeBuffer, (byte *) lpBuffer, nNumberOfBytes); auto iinspectable = (IInspectable *)reinterpret_cast(nativeBuffer.Get()); Streams::IBuffer ^buffer = reinterpret_cast(iinspectable); return buffer; } Statement::Statement(Database^ database, Platform::String^ cmd) { auto rc = (SqliteReturnCode) sqlite3_prepare16_v2(database->RawDatabasePtr, cmd->Data(), -1, &m_statement, 0); if (rc == SqliteReturnCode::Error) throw ref new Platform::InvalidArgumentException("SQL Error"); if (rc != SqliteReturnCode::Ok) throw ref new Platform::COMException(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, rc), L"Can't prepare the statement"); m_noMoreRows = false; } void Statement::ThrowIfStepFailed(SqliteReturnCode rc) { switch (rc) { case SqliteReturnCode::Ok: case SqliteReturnCode::Done: case SqliteReturnCode::MoreRows: break; case SqliteReturnCode::ConstraintViolation: throw ref new Platform::Exception(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, rc), L"Constraint violation"); case SqliteReturnCode::ReadOnlyConstraint: throw ref new Platform::Exception(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, rc), L"Database is read-only"); case SqliteReturnCode::OutOfMemory: throw ref new Platform::OutOfMemoryException(); default: throw ref new Platform::COMException(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, rc), L"Can't execute the statement"); } } void Statement::ThrowIfFailed(HRESULT hr) { if (FAILED(hr)) { throw Exception::CreateException(hr); } } IAsyncOperation^ Statement::StepAsync() { CheckForMoreRows(); return create_async([this]() { auto result = Step(); switch (result) { case SqliteReturnCode::Done: m_noMoreRows = true; break; case SqliteReturnCode::MoreRows: break; default: ThrowIfStepFailed(result); break; } if (!m_noMoreRows && m_columnsEnabled) { if (m_columns == nullptr) m_columns = ref new Platform::Collections::Map(); else m_columns->Clear(); for (auto i = 0; i < GetColumnCount(); i++) { m_columns->Insert(GetColumnName(i), GetTextAt(i)); } } return !m_noMoreRows; }); } SqliteReturnCode Statement::Step(void) { SimulateSlowOperation(); auto result = (SqliteReturnCode) sqlite3_step(m_statement); return result; } int Statement::GetParameterIndex(Platform::String^ name) { auto result = GetParameterIndexNoThrow(name); if (result <= 0) throw ref new Platform::InvalidArgumentException(L"Supplied 'name' doesn't exist"); return result; } int Statement::GetParameterIndexNoThrow(Platform::String^ name) { // parameter index must always be UTF8. auto utf8name = PlatformStringToCharArray(name); int result = sqlite3_bind_parameter_index(m_statement, utf8name.get()); return result; } Statement::~Statement() { sqlite3_finalize(m_statement); } void Statement::CheckForMoreRows() { if (m_noMoreRows) throw ref new Platform::FailureException("No more rows"); } Platform::String^ Statement::GetTextAt(int index) { CheckForMoreRows(); return ref new Platform::String((wchar_t*) sqlite3_column_text16(m_statement, index)); } int Statement::GetIntAt(int index) { CheckForMoreRows(); return sqlite3_column_int(m_statement, index); } int64 Statement::GetInt64At(int index) { CheckForMoreRows(); return sqlite3_column_int64(m_statement, index); } double Statement::GetDoubleAt(int index) { CheckForMoreRows(); return sqlite3_column_double(m_statement, index); } IBuffer^ Statement::GetBlobAt(int index) { CheckForMoreRows(); const void *bytes = sqlite3_column_blob(m_statement, index); Streams::IBuffer ^buffer = CreateNativeBuffer((LPVOID) bytes, sqlite3_column_bytes(m_statement, index)); return buffer; } void ThrowIfBindingFailed(SqliteReturnCode rc) { if (rc == SqliteReturnCode::IndexOutOfRange) throw ref new Platform::OutOfBoundsException(L"Index out of range"); if (rc == SqliteReturnCode::TypeMismatch) throw ref new Platform::InvalidArgumentException(L"Type mis-match"); if (rc != SqliteReturnCode::Ok) throw ref new Platform::COMException(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, rc), L"Couldn't bind value"); } void Statement::BindTextParameterAt(int index, Platform::String^ param) { auto rc = (SqliteReturnCode) sqlite3_bind_text16(m_statement, index, param->Data(), -1, SQLITE_STATIC); ThrowIfBindingFailed(rc); } void Statement::BindIntParameterAt(int index, int param) { auto rc = (SqliteReturnCode) sqlite3_bind_int(m_statement, index, param); ThrowIfBindingFailed(rc); } void Statement::BindInt64ParameterAt(int index, int64 param) { auto rc = (SqliteReturnCode) sqlite3_bind_int64(m_statement, index, param); ThrowIfBindingFailed(rc); } void Statement::BindDoubleParameterAt(int index, double param) { auto rc = (SqliteReturnCode) sqlite3_bind_double(m_statement, index, param); ThrowIfBindingFailed(rc); } void Statement::BindNullParameterAt(int index) { auto rc = (SqliteReturnCode) sqlite3_bind_null(m_statement, index); ThrowIfBindingFailed(rc); } void Statement::BindBlobParameterAt(int index, IBuffer^ param) { if (param == nullptr) throw ref new Platform::NullReferenceException(L"buffer cannot be null"); // To access the buffer, we need to get an interface to IBufferByteAccess Object^ obj = param; ComPtr insp(reinterpret_cast(obj)); // Query IBufferByteAccess ComPtr bufferByteAccess = NULL; ThrowIfFailed(insp.As(&bufferByteAccess)); byte* bytes = nullptr; ThrowIfFailed(bufferByteAccess->Buffer(&bytes)); auto rc = (SqliteReturnCode) sqlite3_bind_blob(m_statement, index, bytes, param->Length, SQLITE_STATIC); ThrowIfBindingFailed(rc); } void Statement::BindIntParameterWithName(Platform::String^ name, int param) { BindPrimitiveParameter(m_statement, name, param, sqlite3_bind_int); } void Statement::BindInt64ParameterWithName(Platform::String^ name, int64 param) { BindPrimitiveParameter(m_statement, name, param, sqlite3_bind_int64); } void Statement::BindDoubleParameterWithName(Platform::String^ name, double param) { BindPrimitiveParameter(m_statement, name, param, sqlite3_bind_double); } void Statement::BindTextParameterWithName(Platform::String^ name, Platform::String^ param) { int index = GetParameterIndexNoThrow(name); if (index < 0) throw ref new Platform::InvalidArgumentException("Column 'name' doesn't exist"); auto rc = (SqliteReturnCode) sqlite3_bind_text16(m_statement, index, param->Data(), -1, SQLITE_STATIC); ThrowIfBindingFailed(rc); } void Statement::BindNullParameterWithName(Platform::String^ name) { int index = GetParameterIndexNoThrow(name); if (index < 0) throw ref new Platform::InvalidArgumentException("Column 'name' doesn't exist"); auto rc = (SqliteReturnCode) sqlite3_bind_null(m_statement, index); ThrowIfBindingFailed(rc); } void Statement::BindBlobParameterWithName(Platform::String^ name, IBuffer^ param) { int index = GetParameterIndexNoThrow(name); if (index < 0) throw ref new Platform::InvalidArgumentException("Column 'name' doesn't exist"); BindBlobParameterAt(index, param); } template void Statement::BindPrimitiveParameter(sqlite3_stmt* statement, Platform::String^ name, T value, int func(sqlite3_stmt*, int, T)) { int index = GetParameterIndexNoThrow(name); if (index < 0) throw ref new Platform::InvalidArgumentException("Column 'name' doesn't exist"); auto rc = (SqliteReturnCode) func(statement, index, value); ThrowIfBindingFailed(rc); } int Statement::GetColumnCount() { return sqlite3_column_count(m_statement); } String^ Statement::GetColumnName(int index) { return ref new String((wchar_t*) sqlite3_column_name16(m_statement, index)); } ColumnType Statement::GetColumnType(int index) { return (ColumnType) sqlite3_column_type(m_statement, index); } void Statement::ClearBindings() { auto rc = (SqliteReturnCode) sqlite3_clear_bindings(m_statement); if (rc != SqliteReturnCode::Ok) throw ref new Platform::COMException(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, rc), L"Couldn't clear bindings"); } void Statement::Reset() { auto rc = (SqliteReturnCode) sqlite3_reset(m_statement); if (rc != SqliteReturnCode::Ok) throw ref new Platform::COMException(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, rc), L"Couldn't reset connection"); m_noMoreRows = false; } IMapView^ Statement::Columns::get() { if (m_columns == nullptr) return nullptr; return m_columns->GetView(); }