From 7f642e51670bc38a4ef782a363936850bc2b0ba9 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 22 Apr 2021 06:38:23 -0700 Subject: Move dutil into libs/dutil --- src/libs/dutil/WixToolset.DUtil/sqlutil.cpp | 1002 +++++++++++++++++++++++++++ 1 file changed, 1002 insertions(+) create mode 100644 src/libs/dutil/WixToolset.DUtil/sqlutil.cpp (limited to 'src/libs/dutil/WixToolset.DUtil/sqlutil.cpp') diff --git a/src/libs/dutil/WixToolset.DUtil/sqlutil.cpp b/src/libs/dutil/WixToolset.DUtil/sqlutil.cpp new file mode 100644 index 00000000..782c7088 --- /dev/null +++ b/src/libs/dutil/WixToolset.DUtil/sqlutil.cpp @@ -0,0 +1,1002 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +// okay, this may look a little weird, but sqlutil.h cannot be in the +// pre-compiled header because we need to #define these things so the +// correct GUID's get pulled into this object file +#include +#define DBINITCONSTANTS +#include "sqlutil.h" + + +//Please note that only SQL native client 11 has TLS1.2 support +#define _SQLNCLI_OLEDB_DEPRECATE_WARNING + +#if !defined(SQLNCLI_VER) +#define SQLNCLI_VER 1100 +#endif + +#if SQLNCLI_VER >= 1100 +#if defined(_SQLNCLI_OLEDB_) || !defined(_SQLNCLI_ODBC_) +#define SQLNCLI_CLSID CLSID_SQLNCLI11 +#endif // defined(_SQLNCLI_OLEDB_) || !defined(_SQLNCLI_ODBC_) +extern const GUID OLEDBDECLSPEC _SQLNCLI_OLEDB_DEPRECATE_WARNING CLSID_SQLNCLI11 = { 0x397C2819L,0x8272,0x4532,{ 0xAD,0x3A,0xFB,0x5E,0x43,0xBE,0xAA,0x39 } }; +#endif // SQLNCLI_VER >= 1100 + +// Exit macros +#define SqlExitTrace(x, s, ...) ExitTraceSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__) +#define SqlExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__) +#define SqlExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__) +#define SqlExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__) +#define SqlExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__) +#define SqlExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__) +#define SqlExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_SQLUTIL, x, s, __VA_ARGS__) +#define SqlExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_SQLUTIL, p, x, e, s, __VA_ARGS__) +#define SqlExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_SQLUTIL, p, x, s, __VA_ARGS__) +#define SqlExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_SQLUTIL, p, x, e, s, __VA_ARGS__) +#define SqlExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_SQLUTIL, p, x, s, __VA_ARGS__) +#define SqlExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_SQLUTIL, e, x, s, __VA_ARGS__) +#define SqlExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_SQLUTIL, g, x, s, __VA_ARGS__) + +// private prototypes +static HRESULT InitializeDatabaseConnection( + __in REFCLSID rclsid, + __in_z LPCSTR szFriendlyClsidName, + __in DBPROPSET rgdbpsetInit[], + __in_ecount(rgdbpsetInit) DWORD cdbpsetInit, + __out IDBCreateSession** ppidbSession + ); +HRESULT DumpErrorRecords(); +static HRESULT FileSpecToString( + __in const SQL_FILESPEC* psf, + __out LPWSTR* ppwz + ); +static HRESULT EscapeSqlIdentifier( + __in_z LPCWSTR wzDatabase, + __deref_out_z LPWSTR* ppwz + ); + + +/******************************************************************** + SqlConnectDatabase - establishes a connection to a database + + NOTE: wzInstance is optional + if fIntegratedAuth is set then wzUser and wzPassword are ignored +********************************************************************/ +extern "C" HRESULT DAPI SqlConnectDatabase( + __in_z LPCWSTR wzServer, + __in_z LPCWSTR wzInstance, + __in_z LPCWSTR wzDatabase, + __in BOOL fIntegratedAuth, + __in_z LPCWSTR wzUser, + __in_z LPCWSTR wzPassword, + __out IDBCreateSession** ppidbSession + ) +{ + Assert(wzServer && wzDatabase && *wzDatabase && ppidbSession); + + HRESULT hr = S_OK; + LPWSTR pwzServerInstance = NULL; + DBPROP rgdbpInit[4] = { }; + DBPROPSET rgdbpsetInit[1] = { }; + ULONG cProperties = 0; + + // if there is an instance + if (wzInstance && *wzInstance) + { + hr = StrAllocFormatted(&pwzServerInstance, L"%s\\%s", wzServer, wzInstance); + } + else + { + hr = StrAllocString(&pwzServerInstance, wzServer, 0); + } + SqlExitOnFailure(hr, "failed to allocate memory for the server instance"); + + // server[\instance] + rgdbpInit[cProperties].dwPropertyID = DBPROP_INIT_DATASOURCE; + rgdbpInit[cProperties].dwOptions = DBPROPOPTIONS_REQUIRED; + rgdbpInit[cProperties].colid = DB_NULLID; + ::VariantInit(&rgdbpInit[cProperties].vValue); + rgdbpInit[cProperties].vValue.vt = VT_BSTR; + rgdbpInit[cProperties].vValue.bstrVal = ::SysAllocString(pwzServerInstance); + ++cProperties; + + // database + rgdbpInit[cProperties].dwPropertyID = DBPROP_INIT_CATALOG; + rgdbpInit[cProperties].dwOptions = DBPROPOPTIONS_REQUIRED; + rgdbpInit[cProperties].colid = DB_NULLID; + ::VariantInit(&rgdbpInit[cProperties].vValue); + rgdbpInit[cProperties].vValue.vt = VT_BSTR; + rgdbpInit[cProperties].vValue.bstrVal= ::SysAllocString(wzDatabase); + ++cProperties; + + if (fIntegratedAuth) + { + // username + rgdbpInit[cProperties].dwPropertyID = DBPROP_AUTH_INTEGRATED; + rgdbpInit[cProperties].dwOptions = DBPROPOPTIONS_REQUIRED; + rgdbpInit[cProperties].colid = DB_NULLID; + ::VariantInit(&rgdbpInit[cProperties].vValue); + rgdbpInit[cProperties].vValue.vt = VT_BSTR; + rgdbpInit[cProperties].vValue.bstrVal = ::SysAllocString(L"SSPI"); // default windows authentication + ++cProperties; + } + else + { + // username + rgdbpInit[cProperties].dwPropertyID = DBPROP_AUTH_USERID; + rgdbpInit[cProperties].dwOptions = DBPROPOPTIONS_REQUIRED; + rgdbpInit[cProperties].colid = DB_NULLID; + ::VariantInit(&rgdbpInit[cProperties].vValue); + rgdbpInit[cProperties].vValue.vt = VT_BSTR; + rgdbpInit[cProperties].vValue.bstrVal = ::SysAllocString(wzUser); + ++cProperties; + + // password + rgdbpInit[cProperties].dwPropertyID = DBPROP_AUTH_PASSWORD; + rgdbpInit[cProperties].dwOptions = DBPROPOPTIONS_REQUIRED; + rgdbpInit[cProperties].colid = DB_NULLID; + ::VariantInit(&rgdbpInit[cProperties].vValue); + rgdbpInit[cProperties].vValue.vt = VT_BSTR; + rgdbpInit[cProperties].vValue.bstrVal = ::SysAllocString(wzPassword); + ++cProperties; + } + + // put the properties into a set + rgdbpsetInit[0].guidPropertySet = DBPROPSET_DBINIT; + rgdbpsetInit[0].rgProperties = rgdbpInit; + rgdbpsetInit[0].cProperties = cProperties; + + // obtain access to the SQL Native Client provider + hr = InitializeDatabaseConnection(SQLNCLI_CLSID, "SQL Native Client", rgdbpsetInit, countof(rgdbpsetInit), ppidbSession); + if (FAILED(hr)) + { + SqlExitTrace(hr, "Could not initialize SQL Native Client, falling back to SQL OLE DB..."); + + // try OLE DB but if that fails return original error failure + HRESULT hr2 = InitializeDatabaseConnection(CLSID_SQLOLEDB, "SQL OLE DB", rgdbpsetInit, countof(rgdbpsetInit), ppidbSession); + if (FAILED(hr2)) + { + SqlExitTrace(hr2, "Could not initialize SQL OLE DB either, giving up."); + } + else + { + hr = S_OK; + } + } + +LExit: + for (; 0 < cProperties; cProperties--) + { + ::VariantClear(&rgdbpInit[cProperties - 1].vValue); + } + + ReleaseStr(pwzServerInstance); + + return hr; +} + + +/******************************************************************** + SqlStartTransaction - Starts a new transaction that must be ended + +*********************************************************************/ +extern "C" HRESULT DAPI SqlStartTransaction( + __in IDBCreateSession* pidbSession, + __out IDBCreateCommand** ppidbCommand, + __out ITransaction** ppit + ) +{ + Assert(pidbSession && ppit); + + HRESULT hr = S_OK; + + hr = pidbSession->CreateSession(NULL, IID_IDBCreateCommand, (IUnknown**)ppidbCommand); + SqlExitOnFailure(hr, "unable to create command from session"); + + hr = (*ppidbCommand)->QueryInterface(IID_ITransactionLocal, (LPVOID*)ppit); + SqlExitOnFailure(hr, "Unable to QueryInterface session to get ITransactionLocal"); + + hr = ((ITransactionLocal*)*ppit)->StartTransaction(ISOLATIONLEVEL_SERIALIZABLE, 0, NULL, NULL); + +LExit: + + return hr; +} + +/******************************************************************** + SqlEndTransaction - Ends the transaction + + NOTE: if fCommit, will commit the transaction, otherwise rolls back +*********************************************************************/ +extern "C" HRESULT DAPI SqlEndTransaction( + __in ITransaction* pit, + __in BOOL fCommit + ) +{ + Assert(pit); + + HRESULT hr = S_OK; + + if (fCommit) + { + hr = pit->Commit(FALSE, XACTTC_SYNC, 0); + SqlExitOnFailure(hr, "commit of transaction failed"); + } + else + { + hr = pit->Abort(NULL, FALSE, FALSE); + SqlExitOnFailure(hr, "abort of transaction failed"); + } + +LExit: + + return hr; +} + + +/******************************************************************** + SqlDatabaseExists - determines if database exists + + NOTE: wzInstance is optional + if fIntegratedAuth is set then wzUser and wzPassword are ignored + returns S_OK if database exist + returns S_FALSE if database does not exist + returns E_* on error +********************************************************************/ +extern "C" HRESULT DAPI SqlDatabaseExists( + __in_z LPCWSTR wzServer, + __in_z LPCWSTR wzInstance, + __in_z LPCWSTR wzDatabase, + __in BOOL fIntegratedAuth, + __in_z LPCWSTR wzUser, + __in_z LPCWSTR wzPassword, + __out_opt BSTR* pbstrErrorDescription + ) +{ + Assert(wzServer && wzDatabase && *wzDatabase); + + HRESULT hr = S_OK; + IDBCreateSession* pidbSession = NULL; + + hr = SqlConnectDatabase(wzServer, wzInstance, L"master", fIntegratedAuth, wzUser, wzPassword, &pidbSession); + SqlExitOnFailure(hr, "failed to connect to 'master' database on server %ls", wzServer); + + hr = SqlSessionDatabaseExists(pidbSession, wzDatabase, pbstrErrorDescription); + +LExit: + ReleaseObject(pidbSession); + + return hr; +} + + +/******************************************************************** + SqlSessionDatabaseExists - determines if database exists + + NOTE: pidbSession must be connected to master database + returns S_OK if database exist + returns S_FALSE if database does not exist + returns E_* on error +********************************************************************/ +extern "C" HRESULT DAPI SqlSessionDatabaseExists( + __in IDBCreateSession* pidbSession, + __in_z LPCWSTR wzDatabase, + __out_opt BSTR* pbstrErrorDescription + ) +{ + Assert(pidbSession && wzDatabase && *wzDatabase); + + HRESULT hr = S_OK; + + LPWSTR pwzQuery = NULL; + IRowset* pirs = NULL; + + DBCOUNTITEM cRows = 0; + HROW rghRows[1]; + HROW* prow = rghRows; + + // + // query to see if the database exists + // + hr = StrAllocFormatted(&pwzQuery, L"SELECT name FROM sysdatabases WHERE name='%s'", wzDatabase); + SqlExitOnFailure(hr, "failed to allocate query string to ensure database exists"); + + hr = SqlSessionExecuteQuery(pidbSession, pwzQuery, &pirs, NULL, pbstrErrorDescription); + SqlExitOnFailure(hr, "failed to get database list from 'master' database"); + Assert(pirs); + + // + // check to see if the database was returned + // + hr = pirs->GetNextRows(DB_NULL_HCHAPTER, 0, 1, &cRows, &prow); + SqlExitOnFailure(hr, "failed to get row with database name"); + + // succeeded but no database + if ((DB_S_ENDOFROWSET == hr) || (0 == cRows)) + { + hr = S_FALSE; + } + +LExit: + ReleaseObject(pirs); + ReleaseStr(pwzQuery); + + return hr; +} + + +/******************************************************************** + SqlDatabaseEnsureExists - creates a database if it does not exist + + NOTE: wzInstance is optional + if fIntegratedAuth is set then wzUser and wzPassword are ignored +********************************************************************/ +extern "C" HRESULT DAPI SqlDatabaseEnsureExists( + __in_z LPCWSTR wzServer, + __in_z LPCWSTR wzInstance, + __in_z LPCWSTR wzDatabase, + __in BOOL fIntegratedAuth, + __in_z LPCWSTR wzUser, + __in_z LPCWSTR wzPassword, + __in_opt const SQL_FILESPEC* psfDatabase, + __in_opt const SQL_FILESPEC* psfLog, + __out_opt BSTR* pbstrErrorDescription + ) +{ + Assert(wzServer && wzDatabase && *wzDatabase); + + HRESULT hr = S_OK; + IDBCreateSession* pidbSession = NULL; + + // + // connect to the master database to create the new database + // + hr = SqlConnectDatabase(wzServer, wzInstance, L"master", fIntegratedAuth, wzUser, wzPassword, &pidbSession); + SqlExitOnFailure(hr, "failed to connect to 'master' database on server %ls", wzServer); + + hr = SqlSessionDatabaseEnsureExists(pidbSession, wzDatabase, psfDatabase, psfLog, pbstrErrorDescription); + SqlExitOnFailure(hr, "failed to create database: %ls", wzDatabase); + + Assert(S_OK == hr); +LExit: + ReleaseObject(pidbSession); + + return hr; +} + + +/******************************************************************** + SqlSessionDatabaseEnsureExists - creates a database if it does not exist + + NOTE: pidbSession must be connected to the master database +********************************************************************/ +extern "C" HRESULT DAPI SqlSessionDatabaseEnsureExists( + __in IDBCreateSession* pidbSession, + __in_z LPCWSTR wzDatabase, + __in_opt const SQL_FILESPEC* psfDatabase, + __in_opt const SQL_FILESPEC* psfLog, + __out_opt BSTR* pbstrErrorDescription + ) +{ + Assert(pidbSession && wzDatabase && *wzDatabase); + + HRESULT hr = S_OK; + + hr = SqlSessionDatabaseExists(pidbSession, wzDatabase, pbstrErrorDescription); + SqlExitOnFailure(hr, "failed to determine if exists, database: %ls", wzDatabase); + + if (S_FALSE == hr) + { + hr = SqlSessionCreateDatabase(pidbSession, wzDatabase, psfDatabase, psfLog, pbstrErrorDescription); + SqlExitOnFailure(hr, "failed to create database: %ls", wzDatabase); + } + // else database already exists, return S_FALSE + + Assert(S_OK == hr); +LExit: + + return hr; +} + + +/******************************************************************** + SqlCreateDatabase - creates a database on the server + + NOTE: wzInstance is optional + if fIntegratedAuth is set then wzUser and wzPassword are ignored +********************************************************************/ +extern "C" HRESULT DAPI SqlCreateDatabase( + __in_z LPCWSTR wzServer, + __in_z LPCWSTR wzInstance, + __in_z LPCWSTR wzDatabase, + __in BOOL fIntegratedAuth, + __in_z LPCWSTR wzUser, + __in_z LPCWSTR wzPassword, + __in_opt const SQL_FILESPEC* psfDatabase, + __in_opt const SQL_FILESPEC* psfLog, + __out_opt BSTR* pbstrErrorDescription + ) +{ + Assert(wzServer && wzDatabase && *wzDatabase); + + HRESULT hr = S_OK; + IDBCreateSession* pidbSession = NULL; + + // + // connect to the master database to create the new database + // + hr = SqlConnectDatabase(wzServer, wzInstance, L"master", fIntegratedAuth, wzUser, wzPassword, &pidbSession); + SqlExitOnFailure(hr, "failed to connect to 'master' database on server %ls", wzServer); + + hr = SqlSessionCreateDatabase(pidbSession, wzDatabase, psfDatabase, psfLog, pbstrErrorDescription); + SqlExitOnFailure(hr, "failed to create database: %ls", wzDatabase); + + Assert(S_OK == hr); +LExit: + ReleaseObject(pidbSession); + + return hr; +} + + +/******************************************************************** + SqlSessionCreateDatabase - creates a database on the server + + NOTE: pidbSession must be connected to the master database +********************************************************************/ +extern "C" HRESULT DAPI SqlSessionCreateDatabase( + __in IDBCreateSession* pidbSession, + __in_z LPCWSTR wzDatabase, + __in_opt const SQL_FILESPEC* psfDatabase, + __in_opt const SQL_FILESPEC* psfLog, + __out_opt BSTR* pbstrErrorDescription + ) +{ + HRESULT hr = S_OK; + LPWSTR pwzDbFile = NULL; + LPWSTR pwzLogFile = NULL; + LPWSTR pwzQuery = NULL; + LPWSTR pwzDatabaseEscaped = NULL; + + if (psfDatabase) + { + hr = FileSpecToString(psfDatabase, &pwzDbFile); + SqlExitOnFailure(hr, "failed to convert db filespec to string"); + } + + if (psfLog) + { + hr = FileSpecToString(psfLog, &pwzLogFile); + SqlExitOnFailure(hr, "failed to convert log filespec to string"); + } + + hr = EscapeSqlIdentifier(wzDatabase, &pwzDatabaseEscaped); + SqlExitOnFailure(hr, "failed to escape database string"); + + hr = StrAllocFormatted(&pwzQuery, L"CREATE DATABASE %s %s%s %s%s", pwzDatabaseEscaped, pwzDbFile ? L"ON " : L"", pwzDbFile ? pwzDbFile : L"", pwzLogFile ? L"LOG ON " : L"", pwzLogFile ? pwzLogFile : L""); + SqlExitOnFailure(hr, "failed to allocate query to create database: %ls", pwzDatabaseEscaped); + + hr = SqlSessionExecuteQuery(pidbSession, pwzQuery, NULL, NULL, pbstrErrorDescription); + SqlExitOnFailure(hr, "failed to create database: %ls, Query: %ls", pwzDatabaseEscaped, pwzQuery); + +LExit: + ReleaseStr(pwzQuery); + ReleaseStr(pwzLogFile); + ReleaseStr(pwzDbFile); + ReleaseStr(pwzDatabaseEscaped); + + return hr; +} + + +/******************************************************************** + SqlDropDatabase - removes a database from a server if it exists + + NOTE: wzInstance is optional + if fIntegratedAuth is set then wzUser and wzPassword are ignored +********************************************************************/ +extern "C" HRESULT DAPI SqlDropDatabase( + __in_z LPCWSTR wzServer, + __in_z LPCWSTR wzInstance, + __in_z LPCWSTR wzDatabase, + __in BOOL fIntegratedAuth, + __in_z LPCWSTR wzUser, + __in_z LPCWSTR wzPassword, + __out_opt BSTR* pbstrErrorDescription + ) +{ + Assert(wzServer && wzDatabase && *wzDatabase); + + HRESULT hr = S_OK; + IDBCreateSession* pidbSession = NULL; + + // + // connect to the master database to search for wzDatabase + // + hr = SqlConnectDatabase(wzServer, wzInstance, L"master", fIntegratedAuth, wzUser, wzPassword, &pidbSession); + SqlExitOnFailure(hr, "Failed to connect to 'master' database"); + + hr = SqlSessionDropDatabase(pidbSession, wzDatabase, pbstrErrorDescription); + +LExit: + ReleaseObject(pidbSession); + + return hr; +} + + +/******************************************************************** + SqlSessionDropDatabase - removes a database from a server if it exists + + NOTE: pidbSession must be connected to the master database +********************************************************************/ +extern "C" HRESULT DAPI SqlSessionDropDatabase( + __in IDBCreateSession* pidbSession, + __in_z LPCWSTR wzDatabase, + __out_opt BSTR* pbstrErrorDescription + ) +{ + Assert(pidbSession && wzDatabase && *wzDatabase); + + HRESULT hr = S_OK; + LPWSTR pwzQuery = NULL; + LPWSTR pwzDatabaseEscaped = NULL; + + hr = SqlSessionDatabaseExists(pidbSession, wzDatabase, pbstrErrorDescription); + SqlExitOnFailure(hr, "failed to determine if exists, database: %ls", wzDatabase); + + hr = EscapeSqlIdentifier(wzDatabase, &pwzDatabaseEscaped); + SqlExitOnFailure(hr, "failed to escape database string"); + + if (S_OK == hr) + { + hr = StrAllocFormatted(&pwzQuery, L"DROP DATABASE %s", pwzDatabaseEscaped); + SqlExitOnFailure(hr, "failed to allocate query to drop database: %ls", pwzDatabaseEscaped); + + hr = SqlSessionExecuteQuery(pidbSession, pwzQuery, NULL, NULL, pbstrErrorDescription); + SqlExitOnFailure(hr, "Failed to drop database"); + } + +LExit: + ReleaseStr(pwzQuery); + ReleaseStr(pwzDatabaseEscaped); + + return hr; +} + + +/******************************************************************** + SqlSessionExecuteQuery - executes a query and returns the results if desired + + NOTE: ppirs and pcRoes and pbstrErrorDescription are optional +********************************************************************/ +extern "C" HRESULT DAPI SqlSessionExecuteQuery( + __in IDBCreateSession* pidbSession, + __in __sql_command LPCWSTR wzSql, + __out_opt IRowset** ppirs, + __out_opt DBROWCOUNT* pcRows, + __out_opt BSTR* pbstrErrorDescription + ) +{ + Assert(pidbSession); + + HRESULT hr = S_OK; + IDBCreateCommand* pidbCommand = NULL; + ICommandText* picmdText = NULL; + ICommand* picmd = NULL; + DBROWCOUNT cRows = 0; + + if (pcRows) + { + *pcRows = NULL; + } + + // + // create the command + // + hr = pidbSession->CreateSession(NULL, IID_IDBCreateCommand, (IUnknown**)&pidbCommand); + SqlExitOnFailure(hr, "failed to create database session"); + hr = pidbCommand->CreateCommand(NULL, IID_ICommand, (IUnknown**)&picmd); + SqlExitOnFailure(hr, "failed to create command to execute session"); + + // + // set the sql text into the command + // + hr = picmd->QueryInterface(IID_ICommandText, (LPVOID*)&picmdText); + SqlExitOnFailure(hr, "failed to get command text object for command"); + hr = picmdText->SetCommandText(DBGUID_DEFAULT , wzSql); + SqlExitOnFailure(hr, "failed to set SQL string: %ls", wzSql); + + // + // execute the command + // + hr = picmd->Execute(NULL, (ppirs) ? IID_IRowset : IID_NULL, NULL, &cRows, reinterpret_cast(ppirs)); + SqlExitOnFailure(hr, "failed to execute SQL string: %ls", wzSql); + + if (DB_S_ERRORSOCCURRED == hr) + { + hr = E_FAIL; + } + + if (pcRows) + { + *pcRows = cRows; + } + +LExit: + + if (FAILED(hr) && picmd && pbstrErrorDescription) + { + HRESULT hrGetErrors = SqlGetErrorInfo(picmd, IID_ICommandText, 0x409, NULL, pbstrErrorDescription); // TODO: use current locale instead of always American-English + if (FAILED(hrGetErrors)) + { + ReleaseBSTR(*pbstrErrorDescription); + } + } + + ReleaseObject(picmd); + ReleaseObject(picmdText); + ReleaseObject(pidbCommand); + + return hr; +} + + +/******************************************************************** + SqlCommandExecuteQuery - executes a SQL command and returns the results if desired + + NOTE: ppirs and pcRoes are optional +********************************************************************/ +extern "C" HRESULT DAPI SqlCommandExecuteQuery( + __in IDBCreateCommand* pidbCommand, + __in __sql_command LPCWSTR wzSql, + __out IRowset** ppirs, + __out DBROWCOUNT* pcRows + ) +{ + Assert(pidbCommand); + + HRESULT hr = S_OK; + ICommandText* picmdText = NULL; + ICommand* picmd = NULL; + DBROWCOUNT cRows = 0; + + if (pcRows) + { + *pcRows = NULL; + } + + // + // create the command + // + hr = pidbCommand->CreateCommand(NULL, IID_ICommand, (IUnknown**)&picmd); + SqlExitOnFailure(hr, "failed to create command to execute session"); + + // + // set the sql text into the command + // + hr = picmd->QueryInterface(IID_ICommandText, (LPVOID*)&picmdText); + SqlExitOnFailure(hr, "failed to get command text object for command"); + hr = picmdText->SetCommandText(DBGUID_DEFAULT , wzSql); + SqlExitOnFailure(hr, "failed to set SQL string: %ls", wzSql); + + // + // execute the command + // + hr = picmd->Execute(NULL, (ppirs) ? IID_IRowset : IID_NULL, NULL, &cRows, reinterpret_cast(ppirs)); + SqlExitOnFailure(hr, "failed to execute SQL string: %ls", wzSql); + + if (DB_S_ERRORSOCCURRED == hr) + { + hr = E_FAIL; + } + + if (pcRows) + { + *pcRows = cRows; + } + +LExit: + ReleaseObject(picmd); + ReleaseObject(picmdText); + + return hr; +} + + +/******************************************************************** + SqlGetErrorInfo - gets error information from the last SQL function call + + NOTE: pbstrErrorSource and pbstrErrorDescription are optional +********************************************************************/ +extern "C" HRESULT DAPI SqlGetErrorInfo( + __in IUnknown* pObjectWithError, + __in REFIID IID_InterfaceWithError, + __in DWORD dwLocaleId, + __out_opt BSTR* pbstrErrorSource, + __out_opt BSTR* pbstrErrorDescription + ) +{ + HRESULT hr = S_OK; + Assert(pObjectWithError); + + // interfaces needed to extract error information out + ISupportErrorInfo* pISupportErrorInfo = NULL; + IErrorInfo* pIErrorInfoAll = NULL; + IErrorRecords* pIErrorRecords = NULL; + IErrorInfo* pIErrorInfoRecord = NULL; + + // only ask for error information if the interface supports it. + hr = pObjectWithError->QueryInterface(IID_ISupportErrorInfo,(void**)&pISupportErrorInfo); + SqlExitOnFailure(hr, "No error information was found for object."); + + hr = pISupportErrorInfo->InterfaceSupportsErrorInfo(IID_InterfaceWithError); + SqlExitOnFailure(hr, "InterfaceWithError is not supported for object with error"); + + // ignore the return of GetErrorInfo it can succeed and return a NULL pointer in pIErrorInfoAll anyway + hr = ::GetErrorInfo(0, &pIErrorInfoAll); + SqlExitOnFailure(hr, "failed to get error info"); + + if (S_OK == hr && pIErrorInfoAll) + { + // see if it's a valid OLE DB IErrorInfo interface that exposes a list of records + hr = pIErrorInfoAll->QueryInterface(IID_IErrorRecords, (void**)&pIErrorRecords); + if (SUCCEEDED(hr)) + { + ULONG cErrors = 0; + pIErrorRecords->GetRecordCount(&cErrors); + + // get the error information for each record + for (ULONG i = 0; i < cErrors; ++i) + { + hr = pIErrorRecords->GetErrorInfo(i, dwLocaleId, &pIErrorInfoRecord); + if (SUCCEEDED(hr)) + { + if (pbstrErrorSource) + { + pIErrorInfoRecord->GetSource(pbstrErrorSource); + } + if (pbstrErrorDescription) + { + pIErrorInfoRecord->GetDescription(pbstrErrorDescription); + } + + ReleaseNullObject(pIErrorInfoRecord); + + break; // TODO: return more than one error in the future! + } + } + + ReleaseNullObject(pIErrorRecords); + } + else // we have a simple error record + { + if (pbstrErrorSource) + { + pIErrorInfoAll->GetSource(pbstrErrorSource); + } + if (pbstrErrorDescription) + { + pIErrorInfoAll->GetDescription(pbstrErrorDescription); + } + } + } + else + { + hr = E_NOMOREITEMS; + } + +LExit: + ReleaseObject(pIErrorInfoRecord); + ReleaseObject(pIErrorRecords); + ReleaseObject(pIErrorInfoAll); + ReleaseObject(pISupportErrorInfo); + + return hr; +} + + +// +// private +// + +static HRESULT InitializeDatabaseConnection( + __in REFCLSID rclsid, + __in_z LPCSTR szFriendlyClsidName, + __in DBPROPSET rgdbpsetInit[], + __in_ecount(rgdbpsetInit) DWORD cdbpsetInit, + __out IDBCreateSession** ppidbSession +) +{ + Unused(szFriendlyClsidName); // only used in DEBUG builds + + HRESULT hr = S_OK; + IDBInitialize* pidbInitialize = NULL; + IDBProperties* pidbProperties = NULL; + + hr = ::CoCreateInstance(rclsid, NULL, CLSCTX_INPROC_SERVER, IID_IDBInitialize, (LPVOID*)&pidbInitialize); + SqlExitOnFailure(hr, "failed to initialize %s", szFriendlyClsidName); + + // create and set the property set + hr = pidbInitialize->QueryInterface(IID_IDBProperties, (LPVOID*)&pidbProperties); + SqlExitOnFailure(hr, "failed to get IID_IDBProperties for %s", szFriendlyClsidName); + + hr = pidbProperties->SetProperties(cdbpsetInit, rgdbpsetInit); + SqlExitOnFailure(hr, "failed to set properties for %s", szFriendlyClsidName); + + // initialize connection to datasource + hr = pidbInitialize->Initialize(); + if (FAILED(hr)) + { + DumpErrorRecords(); + } + SqlExitOnFailure(hr, "failed to initialize connection for %s", szFriendlyClsidName); + + hr = pidbInitialize->QueryInterface(IID_IDBCreateSession, (LPVOID*)ppidbSession); + SqlExitOnFailure(hr, "failed to query for connection session for %s", szFriendlyClsidName); + +LExit: + ReleaseObject(pidbProperties); + ReleaseObject(pidbInitialize); + + return hr; +} + +HRESULT DumpErrorRecords() +{ + HRESULT hr = S_OK; + IErrorInfo* pIErrorInfo = NULL; + IErrorRecords* pIErrorRecords = NULL; + IErrorInfo* pIErrorInfoRecord = NULL; + BSTR bstrDescription = NULL; + ULONG i = 0; + ULONG cRecords = 0; + ERRORINFO ErrorInfo = { }; + + // Get IErrorInfo pointer from OLE. + hr = ::GetErrorInfo(0, &pIErrorInfo); + if (FAILED(hr)) + { + ExitFunction(); + } + + // QI for IID_IErrorRecords. + hr = pIErrorInfo->QueryInterface(IID_IErrorRecords, (void**)&pIErrorRecords); + if (FAILED(hr)) + { + ExitFunction(); + } + + // Get error record count. + hr = pIErrorRecords->GetRecordCount(&cRecords); + if (FAILED(hr)) + { + ExitFunction(); + } + + // Loop through the error records. + for (i = 0; i < cRecords; i++) + { + // Get pIErrorInfo from pIErrorRecords. + hr = pIErrorRecords->GetErrorInfo(i, 1033, &pIErrorInfoRecord); + + if (SUCCEEDED(hr)) + { + // Get error description and source. + hr = pIErrorInfoRecord->GetDescription(&bstrDescription); + + // Retrieve the ErrorInfo structures. + hr = pIErrorRecords->GetBasicErrorInfo(i, &ErrorInfo); + + SqlExitTrace(ErrorInfo.hrError, "SQL error %lu/%lu: %ls", i + 1, cRecords, bstrDescription); + + ReleaseNullObject(pIErrorInfoRecord); + ReleaseNullBSTR(bstrDescription); + } + } + +LExit: + ReleaseNullBSTR(bstrDescription); + ReleaseObject(pIErrorInfoRecord); + ReleaseObject(pIErrorRecords); + ReleaseObject(pIErrorInfo); + + return hr; +} + +/******************************************************************** + FileSpecToString + +*********************************************************************/ +static HRESULT FileSpecToString( + __in const SQL_FILESPEC* psf, + __out LPWSTR* ppwz + ) +{ + Assert(psf && ppwz); + + HRESULT hr = S_OK; + LPWSTR pwz = NULL; + + hr = StrAllocString(&pwz, L"(", 1024); + SqlExitOnFailure(hr, "failed to allocate string for database file info"); + + SqlExitOnNull(*psf->wzName, hr, E_INVALIDARG, "logical name not specified in database file info"); + SqlExitOnNull(*psf->wzFilename, hr, E_INVALIDARG, "filename not specified in database file info"); + + hr = StrAllocFormatted(&pwz, L"%sNAME=%s", pwz, psf->wzName); + SqlExitOnFailure(hr, "failed to format database file info name: %ls", psf->wzName); + + hr = StrAllocFormatted(&pwz, L"%s, FILENAME='%s'", pwz, psf->wzFilename); + SqlExitOnFailure(hr, "failed to format database file info filename: %ls", psf->wzFilename); + + if (0 != psf->wzSize[0]) + { + hr = StrAllocFormatted(&pwz, L"%s, SIZE=%s", pwz, psf->wzSize); + SqlExitOnFailure(hr, "failed to format database file info size: %ls", psf->wzSize); + } + + if (0 != psf->wzMaxSize[0]) + { + hr = StrAllocFormatted(&pwz, L"%s, MAXSIZE=%s", pwz, psf->wzMaxSize); + SqlExitOnFailure(hr, "failed to format database file info maxsize: %ls", psf->wzMaxSize); + } + + if (0 != psf->wzGrow[0]) + { + hr = StrAllocFormatted(&pwz, L"%s, FILEGROWTH=%s", pwz, psf->wzGrow); + SqlExitOnFailure(hr, "failed to format database file info growth: %ls", psf->wzGrow); + } + + hr = StrAllocFormatted(&pwz, L"%s)", pwz); + SqlExitOnFailure(hr, "failed to allocate string for file spec"); + + *ppwz = pwz; + pwz = NULL; // null here so it doesn't get freed below + +LExit: + ReleaseStr(pwz); + return hr; +} + +static HRESULT EscapeSqlIdentifier( + __in_z LPCWSTR wzIdentifier, + __deref_out_z LPWSTR* ppwz + ) +{ + Assert(ppwz); + + HRESULT hr = S_OK; + LPWSTR pwz = NULL; + + if (wzIdentifier == NULL) + { + //Just ignore a NULL identifier and clear out the result + ReleaseNullStr(*ppwz); + ExitFunction(); + } + + int cchIdentifier = lstrlenW(wzIdentifier); + + //If an empty string or already escaped just copy + if (cchIdentifier == 0 || (wzIdentifier[0] == '[' && wzIdentifier[cchIdentifier-1] == ']')) + { + hr = StrAllocString(&pwz, wzIdentifier, 0); + SqlExitOnFailure(hr, "failed to format database name: %ls", wzIdentifier); + } + else + { + //escape it + hr = StrAllocFormatted(&pwz, L"[%s]", wzIdentifier); + SqlExitOnFailure(hr, "failed to format escaped database name: %ls", wzIdentifier); + } + + *ppwz = pwz; + pwz = NULL; // null here so it doesn't get freed below + +LExit: + ReleaseStr(pwz); + return hr; +} -- cgit v1.2.3-55-g6feb