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/eseutil.cpp | 1340 +++++++++++++++++++++++++++ 1 file changed, 1340 insertions(+) create mode 100644 src/libs/dutil/WixToolset.DUtil/eseutil.cpp (limited to 'src/libs/dutil/WixToolset.DUtil/eseutil.cpp') diff --git a/src/libs/dutil/WixToolset.DUtil/eseutil.cpp b/src/libs/dutil/WixToolset.DUtil/eseutil.cpp new file mode 100644 index 00000000..b9455d4b --- /dev/null +++ b/src/libs/dutil/WixToolset.DUtil/eseutil.cpp @@ -0,0 +1,1340 @@ +// 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" + + +// Exit macros +#define EseExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__) +#define EseExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__) +#define EseExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__) +#define EseExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__) +#define EseExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__) +#define EseExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__) +#define EseExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_ESEUTIL, p, x, e, s, __VA_ARGS__) +#define EseExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_ESEUTIL, p, x, s, __VA_ARGS__) +#define EseExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_ESEUTIL, p, x, e, s, __VA_ARGS__) +#define EseExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_ESEUTIL, p, x, s, __VA_ARGS__) +#define EseExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_ESEUTIL, e, x, s, __VA_ARGS__) +#define EseExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_ESEUTIL, g, x, s, __VA_ARGS__) + +struct ESE_QUERY +{ + ESE_QUERY_TYPE qtQueryType; + BOOL fIndexRangeSet; + + JET_SESID jsSession; + JET_TABLEID jtTable; + + DWORD dwColumns; + void *pvData[10]; // The data queried for for this column + DWORD cbData[10]; // One for each column, describes the size of the corresponding entry in ppvData +}; + +// Todo: convert more JET_ERR to HRESULTS here +HRESULT HresultFromJetError(JET_ERR jEr) +{ + HRESULT hr = S_OK; + + switch (jEr) + { + case JET_errSuccess: + return S_OK; + + case JET_wrnNyi: + return E_NOTIMPL; + break; + + case JET_errOutOfMemory: + hr = E_OUTOFMEMORY; + break; + + case JET_errInvalidParameter: __fallthrough; + case JET_errInvalidInstance: + hr = E_INVALIDARG; + break; + + case JET_errDatabaseInUse: + hr = HRESULT_FROM_WIN32(ERROR_DEVICE_IN_USE); + break; + + case JET_errKeyDuplicate: + hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + break; + + case JET_errInvalidSystemPath: __fallthrough; + case JET_errInvalidLogDirectory: __fallthrough; + case JET_errInvalidPath: __fallthrough; + case JET_errDatabaseInvalidPath: + hr = HRESULT_FROM_WIN32(ERROR_INVALID_NAME); + break; + + case JET_errDatabaseLocked: + hr = HRESULT_FROM_WIN32(ERROR_FILE_CHECKED_OUT); + break; + + case JET_errInvalidDatabase: + hr = HRESULT_FROM_WIN32(ERROR_INTERNAL_DB_CORRUPTION); + break; + + case JET_errCallbackNotResolved: + hr = HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + break; + + case JET_errNoCurrentRecord: __fallthrough; + case JET_errRecordNotFound: __fallthrough; + case JET_errFileNotFound: __fallthrough; + case JET_errObjectNotFound: + hr = E_NOTFOUND; + break; + + case JET_wrnBufferTruncated: + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + break; + + case JET_errFileAccessDenied: + hr = E_ACCESSDENIED; + break; + + default: + hr = E_FAIL; + } + + // Log the actual Jet error code so we have record of it before it's morphed into an HRESULT to be compatible with the rest of our code + ExitTraceSource(DUTIL_SOURCE_ESEUTIL, hr, "Encountered Jet Error: 0x%08x", jEr); + + return hr; +} + +#define ExitOnJetFailure(e, x, s, ...) { x = HresultFromJetError(e); if (S_OK != x) { ExitTraceSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__); goto LExit; }} +#define ExitOnRootJetFailure(e, x, s, ...) { x = HresultFromJetError(e); if (S_OK != x) { Dutil_RootFailure(__FILE__, __LINE__, x); ExitTraceSource(DUTIL_SOURCE_ESEUTIL, x, s, __VA_ARGS__); goto LExit; }} + +HRESULT DAPI EseBeginSession( + __out JET_INSTANCE *pjiInstance, + __out JET_SESID *pjsSession, + __in_z LPCWSTR pszInstance, + __in_z LPCWSTR pszPath + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + LPSTR pszAnsiInstance = NULL; + LPSTR pszAnsiPath = NULL; + + hr = DirEnsureExists(pszPath, NULL); + EseExitOnFailure(hr, "Failed to ensure database directory exists"); + + // Sigh. JETblue requires Vista and up for the wide character version of this function, so we'll convert to ANSI before calling, + // likely breaking everyone with unicode characters in their path. + hr = StrAnsiAllocString(&pszAnsiInstance, pszInstance, 0, CP_ACP); + EseExitOnFailure(hr, "Failed converting instance name to ansi"); + + hr = StrAnsiAllocString(&pszAnsiPath, pszPath, 0, CP_ACP); + EseExitOnFailure(hr, "Failed converting session path name to ansi"); + + jEr = JetCreateInstanceA(pjiInstance, pszAnsiInstance); + ExitOnJetFailure(jEr, hr, "Failed to create instance"); + + jEr = JetSetSystemParameter(pjiInstance, NULL, JET_paramSystemPath, NULL, pszAnsiPath); + ExitOnJetFailure(jEr, hr, "Failed to set jet system path to: %s", pszAnsiPath); + + // This makes sure log files that are created are created next to the database, not next to our EXE (note they last after execution) + jEr = JetSetSystemParameter(pjiInstance, NULL, JET_paramLogFilePath, NULL, pszAnsiPath); + ExitOnJetFailure(jEr, hr, "Failed to set jet log file path to: %s", pszAnsiPath); + + jEr = JetSetSystemParameter(pjiInstance, NULL, JET_paramMaxOpenTables, 10, NULL); + ExitOnJetFailure(jEr, hr, "Failed to set jet max open tables parameter"); + + // TODO: Use callback hooks so that Jet Engine uses our memory allocation methods, etc.? (search docs for "JET_PFNREALLOC" - there are other callbacks too) + + jEr = JetInit(pjiInstance); + ExitOnJetFailure(jEr, hr, "Failed to initialize jet engine instance"); + + jEr = JetBeginSession(*pjiInstance, pjsSession, NULL, NULL); + ExitOnJetFailure(jEr, hr, "Failed to begin jet session"); + +LExit: + ReleaseStr(pszAnsiInstance); + ReleaseStr(pszAnsiPath); + + return hr; +} + +HRESULT DAPI EseEndSession( + __in JET_INSTANCE jiInstance, + __in JET_SESID jsSession + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + jEr = JetEndSession(jsSession, 0); + ExitOnJetFailure(jEr, hr, "Failed to end jet session"); + + jEr = JetTerm(jiInstance); + ExitOnJetFailure(jEr, hr, "Failed to uninitialize jet engine instance"); + +LExit: + return hr; +} + +// Utility function used by EnsureSchema() +HRESULT AllocColumnCreateStruct( + __in const ESE_TABLE_SCHEMA *ptsSchema, + __deref_out JET_COLUMNCREATE **ppjccColumnCreate + ) +{ + HRESULT hr = S_OK; + DWORD_PTR i; + size_t cbAllocSize = 0; + + hr = ::SizeTMult(ptsSchema->dwColumns, sizeof(JET_COLUMNCREATE), &(cbAllocSize)); + EseExitOnFailure(hr, "Maximum allocation exceeded."); + + *ppjccColumnCreate = static_cast(MemAlloc(cbAllocSize, TRUE)); + EseExitOnNull(*ppjccColumnCreate, hr, E_OUTOFMEMORY, "Failed to allocate column create structure for database"); + + for (i = 0; i < ptsSchema->dwColumns; ++i) + { + (*ppjccColumnCreate)[i].cbStruct = sizeof(JET_COLUMNCREATE); + + hr = StrAnsiAllocString(&(*ppjccColumnCreate)[i].szColumnName, ptsSchema->pcsColumns[i].pszName, 0, CP_ACP); + EseExitOnFailure(hr, "Failed to allocate ansi column name: %ls", ptsSchema->pcsColumns[i].pszName); + + (*ppjccColumnCreate)[i].coltyp = ptsSchema->pcsColumns[i].jcColumnType; + + if (JET_coltypText == (*ppjccColumnCreate)[i].coltyp) + { + (*ppjccColumnCreate)[i].cbMax = 256; + } + else if (JET_coltypLongText == (*ppjccColumnCreate)[i].coltyp) + { + (*ppjccColumnCreate)[i].cbMax = 2147483648; + (*ppjccColumnCreate)[i].grbit = JET_bitColumnTagged; // LongText columns must be tagged + ptsSchema->pcsColumns[i].fNullable = TRUE; + } + else if (JET_coltypLong == (*ppjccColumnCreate)[i].coltyp) + { + (*ppjccColumnCreate)[i].cbMax = 4; + + if (ptsSchema->pcsColumns[i].fAutoIncrement) + { + (*ppjccColumnCreate)[i].grbit |= JET_bitColumnAutoincrement; + } + } + + if (!(ptsSchema->pcsColumns[i].fNullable)) + { + (*ppjccColumnCreate)[i].grbit |= JET_bitColumnNotNULL; + } + + (*ppjccColumnCreate)[i].pvDefault = NULL; + (*ppjccColumnCreate)[i].cbDefault = 0; + (*ppjccColumnCreate)[i].cp = 1200; + (*ppjccColumnCreate)[i].columnid = 0; + (*ppjccColumnCreate)[i].err = 0; + } + +LExit: + return hr; +} + +HRESULT FreeColumnCreateStruct( + __in_ecount(dwColumns) JET_COLUMNCREATE *pjccColumnCreate, + __in DWORD dwColumns + ) +{ + HRESULT hr = S_OK; + DWORD i; + + for (i = 0; i < dwColumns; ++i) + { + ReleaseStr((pjccColumnCreate[i]).szColumnName); + } + + hr = MemFree(pjccColumnCreate); + EseExitOnFailure(hr, "Failed to release core column create struct"); + +LExit: + return hr; +} + +// Utility function used by EnsureSchema() +HRESULT AllocIndexCreateStruct( + __in const ESE_TABLE_SCHEMA *ptsSchema, + __deref_out JET_INDEXCREATE **ppjicIndexCreate + ) +{ + HRESULT hr = S_OK; + LPSTR pszMultiSzKeys = NULL; + LPSTR pszIndexName = NULL; + LPSTR pszTempString = NULL; + BOOL fKeyColumns = FALSE; + DWORD_PTR i; + + for (i=0; i < ptsSchema->dwColumns; ++i) + { + if (ptsSchema->pcsColumns[i].fKey) + { + hr = StrAnsiAllocString(&pszTempString, ptsSchema->pcsColumns[i].pszName, 0, CP_ACP); + EseExitOnFailure(hr, "Failed to convert string to ansi: %ls", ptsSchema->pcsColumns[i].pszName); + + hr = StrAnsiAllocConcat(&pszMultiSzKeys, "+", 0); + EseExitOnFailure(hr, "Failed to append plus sign to multisz string: %s", pszTempString); + + hr = StrAnsiAllocConcat(&pszMultiSzKeys, pszTempString, 0); + EseExitOnFailure(hr, "Failed to append column name to multisz string: %s", pszTempString); + + ReleaseNullStr(pszTempString); + + // All question marks will be converted to null characters later; this is just to trick dutil + // into letting us create an ansi, double-null-terminated list of single-null-terminated strings + hr = StrAnsiAllocConcat(&pszMultiSzKeys, "?", 0); + EseExitOnFailure(hr, "Failed to append placeholder character to multisz string: %hs", pszMultiSzKeys); + + // Record that at least one key column was found + fKeyColumns = TRUE; + } + } + + // If no key columns were found, don't create an index - just return + if (!fKeyColumns) + { + ExitFunction1(hr = S_OK); + } + + hr = StrAnsiAllocString(&pszIndexName, ptsSchema->pszName, 0, CP_ACP); + EseExitOnFailure(hr, "Failed to allocate ansi string version of %ls", ptsSchema->pszName); + + hr = StrAnsiAllocConcat(&pszIndexName, "_Index", 0); + EseExitOnFailure(hr, "Failed to append table name string version of %ls", ptsSchema->pszName); + + *ppjicIndexCreate = static_cast(MemAlloc(sizeof(JET_INDEXCREATE), TRUE)); + EseExitOnNull(*ppjicIndexCreate, hr, E_OUTOFMEMORY, "Failed to allocate index create structure for database"); + + // Record the size including both null terminators - the struct requires this + size_t cchSize = 0; + hr = ::StringCchLengthA(pszMultiSzKeys, STRSAFE_MAX_LENGTH, &cchSize); + EseExitOnRootFailure(hr, "Failed to get size of keys string"); + + ++cchSize; // add 1 to include null character at the end + + // At this point convert all question marks to null characters + for (i = 0; i < cchSize; ++i) + { + if ('?' == pszMultiSzKeys[i]) + { + pszMultiSzKeys[i] = '\0'; + } + } + + (*ppjicIndexCreate)->cbStruct = sizeof(JET_INDEXCREATE); + (*ppjicIndexCreate)->szIndexName = pszIndexName; + (*ppjicIndexCreate)->szKey = pszMultiSzKeys; + (*ppjicIndexCreate)->cbKey = (DWORD)cchSize; + (*ppjicIndexCreate)->grbit = JET_bitIndexUnique | JET_bitIndexPrimary; + (*ppjicIndexCreate)->ulDensity = 80; + (*ppjicIndexCreate)->lcid = 1033; + (*ppjicIndexCreate)->pidxunicode = NULL; + (*ppjicIndexCreate)->cbVarSegMac = 0; + (*ppjicIndexCreate)->rgconditionalcolumn = NULL; + (*ppjicIndexCreate)->cConditionalColumn = 0; + (*ppjicIndexCreate)->err = 0; + +LExit: + ReleaseStr(pszTempString); + + return hr; +} + +HRESULT EnsureSchema( + __in JET_DBID jdbDb, + __in JET_SESID jsSession, + __in ESE_DATABASE_SCHEMA *pdsSchema + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + BOOL fTransaction = FALSE; + DWORD dwTable; + DWORD dwColumn; + JET_TABLECREATE jtTableCreate = { }; + + // Set parameters which apply to all tables here + jtTableCreate.cbStruct = sizeof(jtTableCreate); + jtTableCreate.ulPages = 100; + jtTableCreate.ulDensity = 0; // per the docs, 0 means "use the default value" + jtTableCreate.cIndexes = 1; + + hr = EseBeginTransaction(jsSession); + EseExitOnFailure(hr, "Failed to begin transaction to create tables"); + fTransaction = TRUE; + + for (dwTable = 0;dwTable < pdsSchema->dwTables; ++dwTable) + { + // Don't free this pointer - it's just a shortcut to the current table's name within the struct + LPCWSTR pwzTableName = pdsSchema->ptsTables[dwTable].pszName; + + // Ensure table exists + hr = EseOpenTable(jsSession, jdbDb, pwzTableName, &pdsSchema->ptsTables[dwTable].jtTable); + if (E_NOTFOUND == hr) // if the table is missing, create it + { + // Fill out the JET_TABLECREATE struct + hr = StrAnsiAllocString(&jtTableCreate.szTableName, pdsSchema->ptsTables[dwTable].pszName, 0, CP_ACP); + EseExitOnFailure(hr, "Failed converting table name to ansi"); + + hr = AllocColumnCreateStruct(&(pdsSchema->ptsTables[dwTable]), &jtTableCreate.rgcolumncreate); + EseExitOnFailure(hr, "Failed to allocate column create struct"); + + hr = AllocIndexCreateStruct(&(pdsSchema->ptsTables[dwTable]), &jtTableCreate.rgindexcreate); + EseExitOnFailure(hr, "Failed to allocate index create struct"); + + jtTableCreate.cColumns = pdsSchema->ptsTables[dwTable].dwColumns; + jtTableCreate.tableid = NULL; + + // TODO: Investigate why we can't create a table without a key column? + // Actually create the table using our JET_TABLECREATE struct + jEr = JetCreateTableColumnIndex(jsSession, jdbDb, &jtTableCreate); + ExitOnJetFailure(jEr, hr, "Failed to create %ls table", pwzTableName); + + // Record the table ID in our cache + pdsSchema->ptsTables[dwTable].jtTable = jtTableCreate.tableid; + + // Record the column IDs in our cache + for (dwColumn = 0; dwColumn < pdsSchema->ptsTables[dwTable].dwColumns; ++dwColumn) + { + pdsSchema->ptsTables[dwTable].pcsColumns[dwColumn].jcColumn = jtTableCreate.rgcolumncreate[dwColumn].columnid; + } + + // Free and NULL things we allocated in this struct + ReleaseNullStr(jtTableCreate.szTableName); + + hr = FreeColumnCreateStruct(jtTableCreate.rgcolumncreate, jtTableCreate.cColumns); + EseExitOnFailure(hr, "Failed to free column create struct"); + jtTableCreate.rgcolumncreate = NULL; + } + else + { + // If the table already exists, grab the column ids and put them into our cache + for (dwColumn = 0;dwColumn < pdsSchema->ptsTables[dwTable].dwColumns; ++dwColumn) + { + // Don't free this - it's just a shortcut to the current column within the struct + ESE_COLUMN_SCHEMA *pcsColumn = &(pdsSchema->ptsTables[dwTable].pcsColumns[dwColumn]); + ULONG ulColumnSize = 0; + BOOL fNullable = pcsColumn->fNullable; + + // Todo: this code is nearly duplicated from AllocColumnCreateStruct - factor it out! + if (JET_coltypText == pcsColumn->jcColumnType) + { + ulColumnSize = 256; + } + else if (JET_coltypLongText == pcsColumn->jcColumnType) + { + ulColumnSize = 2147483648; + fNullable = TRUE; + } + else if (JET_coltypLong == pcsColumn->jcColumnType) + { + ulColumnSize = 4; + fNullable = TRUE; + } + + hr = EseEnsureColumn(jsSession, pdsSchema->ptsTables[dwTable].jtTable, pcsColumn->pszName, pcsColumn->jcColumnType, ulColumnSize, pcsColumn->fFixed, fNullable, &pcsColumn->jcColumn); + EseExitOnFailure(hr, "Failed to create column %u of %ls table", dwColumn, pwzTableName); + } + } + } + +LExit: + ReleaseStr(jtTableCreate.szTableName); + + if (NULL != jtTableCreate.rgcolumncreate) + { + // Don't record the HRESULT here or it will override the return value of this function + FreeColumnCreateStruct(jtTableCreate.rgcolumncreate, jtTableCreate.cColumns); + } + + if (fTransaction) + { + EseCommitTransaction(jsSession); + } + + return hr; +} + +// Todo: support overwrite flag? Unfortunately, requires WinXP and up +// Todo: Add version parameter, and a built-in dutil table that stores the version of the database schema on disk - then allow overriding the "migrate to new schema" functionality with a callback +HRESULT DAPI EseEnsureDatabase( + __in JET_SESID jsSession, + __in_z LPCWSTR pszFile, + __in ESE_DATABASE_SCHEMA *pdsSchema, + __out JET_DBID* pjdbDb, + __in BOOL fExclusive, + __in BOOL fReadonly + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + JET_GRBIT jgrOptions = 0; + LPWSTR pszDir = NULL; + LPSTR pszAnsiFile = NULL; + + // Sigh. JETblue requires Vista and up for the wide character version of this function, so we'll convert to ANSI before calling, + // likely breaking all those with unicode characters in their path. + hr = StrAnsiAllocString(&pszAnsiFile, pszFile, 0, CP_ACP); + EseExitOnFailure(hr, "Failed converting database name to ansi"); + + hr = PathGetDirectory(pszFile, &pszDir); + EseExitOnFailure(hr, "Failed to get directory that will contain database file"); + + hr = DirEnsureExists(pszDir, NULL); + EseExitOnFailure(hr, "Failed to ensure directory exists for database: %ls", pszDir); + + if (FileExistsEx(pszFile, NULL)) + { + if (fReadonly) + { + jgrOptions = jgrOptions | JET_bitDbReadOnly; + } + + jEr = JetAttachDatabaseA(jsSession, pszAnsiFile, jgrOptions); + ExitOnJetFailure(jEr, hr, "Failed to attach to database %s", pszAnsiFile); + + // This flag doesn't apply to attach, only applies to Open, so only set it after the attach + if (fExclusive) + { + jgrOptions = jgrOptions | JET_bitDbExclusive; + } + + jEr = JetOpenDatabaseA(jsSession, pszAnsiFile, NULL, pjdbDb, jgrOptions); + ExitOnJetFailure(jEr, hr, "Failed to open database %s", pszAnsiFile); + } + else + { + jEr = JetCreateDatabase2A(jsSession, pszAnsiFile, 0, pjdbDb, 0); + ExitOnJetFailure(jEr, hr, "Failed to create database %ls", pszFile); + } + + hr = EnsureSchema(*pjdbDb, jsSession, pdsSchema); + EseExitOnFailure(hr, "Failed to ensure database schema matches expectations"); + +LExit: + ReleaseStr(pszDir); + ReleaseStr(pszAnsiFile); + + return hr; +} + +HRESULT DAPI EseCloseDatabase( + __in JET_SESID jsSession, + __in JET_DBID jdbDb + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + JET_GRBIT jgrOptions = 0; + + jEr = JetCloseDatabase(jsSession, jdbDb, jgrOptions); + ExitOnJetFailure(jEr, hr, "Failed to close database"); + +LExit: + return hr; +} + +HRESULT DAPI EseCreateTable( + __in JET_SESID jsSession, + __in JET_DBID jdbDb, + __in_z LPCWSTR pszTable, + __out JET_TABLEID *pjtTable + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + LPSTR pszAnsiTable = NULL; + + hr = StrAnsiAllocString(&pszAnsiTable, pszTable, 0, CP_ACP); + EseExitOnFailure(hr, "Failed converting table name to ansi"); + + jEr = JetCreateTableA(jsSession, jdbDb, pszAnsiTable, 100, 0, pjtTable); + ExitOnJetFailure(jEr, hr, "Failed to create table %s", pszAnsiTable); + +LExit: + ReleaseStr(pszAnsiTable); + + return hr; +} + +HRESULT DAPI EseOpenTable( + __in JET_SESID jsSession, + __in JET_DBID jdbDb, + __in_z LPCWSTR pszTable, + __out JET_TABLEID *pjtTable + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + LPSTR pszAnsiTable = NULL; + + hr = StrAnsiAllocString(&pszAnsiTable, pszTable, 0, CP_ACP); + EseExitOnFailure(hr, "Failed converting table name to ansi"); + + jEr = JetOpenTableA(jsSession, jdbDb, pszAnsiTable, NULL, 0, 0, pjtTable); + ExitOnJetFailure(jEr, hr, "Failed to open table %s", pszAnsiTable); + +LExit: + ReleaseStr(pszAnsiTable); + + return hr; +} + +HRESULT DAPI EseCloseTable( + __in JET_SESID jsSession, + __in JET_TABLEID jtTable + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + jEr = JetCloseTable(jsSession, jtTable); + ExitOnJetFailure(jEr, hr, "Failed to close table"); + +LExit: + return hr; +} + +HRESULT DAPI EseEnsureColumn( + __in JET_SESID jsSession, + __in JET_TABLEID jtTable, + __in_z LPCWSTR pszColumnName, + __in JET_COLTYP jcColumnType, + __in ULONG ulColumnSize, + __in BOOL fFixed, + __in BOOL fNullable, + __out_opt JET_COLUMNID *pjcColumn + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + LPSTR pszAnsiColumnName = NULL; + JET_COLUMNDEF jcdColumnDef = { sizeof(JET_COLUMNDEF) }; + JET_COLUMNBASE jcdTempBase = { sizeof(JET_COLUMNBASE) }; + + hr = StrAnsiAllocString(&pszAnsiColumnName, pszColumnName, 0, CP_ACP); + EseExitOnFailure(hr, "Failed converting column name to ansi"); + + jEr = JetGetTableColumnInfoA(jsSession, jtTable, pszAnsiColumnName, &jcdTempBase, sizeof(JET_COLUMNBASE), JET_ColInfoBase); + if (JET_errSuccess == jEr) + { + // Return the found columnID + if (NULL != pjcColumn) + { + *pjcColumn = jcdTempBase.columnid; + } + + ExitFunction1(hr = S_OK); + } + else if (JET_errColumnNotFound == jEr) + { + jEr = JET_errSuccess; + } + ExitOnJetFailure(jEr, hr, "Failed to check if column exists: %s", pszAnsiColumnName); + + jcdColumnDef.columnid = 0; + jcdColumnDef.coltyp = jcColumnType; + jcdColumnDef.wCountry = 0; + jcdColumnDef.langid = 0; + jcdColumnDef.cp = 1200; + jcdColumnDef.wCollate = 0; + jcdColumnDef.cbMax = ulColumnSize; + jcdColumnDef.grbit = 0; + + if (fFixed) + { + jcdColumnDef.grbit = jcdColumnDef.grbit | JET_bitColumnFixed; + } + if (!fNullable) + { + jcdColumnDef.grbit = jcdColumnDef.grbit | JET_bitColumnNotNULL; + } + + jEr = JetAddColumnA(jsSession, jtTable, pszAnsiColumnName, &jcdColumnDef, NULL, 0, pjcColumn); + ExitOnJetFailure(jEr, hr, "Failed to add column %ls", pszColumnName); + +LExit: + ReleaseStr(pszAnsiColumnName); + + return hr; +} + +HRESULT DAPI EseGetColumn( + __in JET_SESID jsSession, + __in JET_TABLEID jtTable, + __in_z LPCWSTR pszColumnName, + __out JET_COLUMNID *pjcColumn + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + LPSTR pszAnsiColumnName = NULL; + JET_COLUMNBASE jcdTempBase = { sizeof(JET_COLUMNBASE) }; + + hr = StrAnsiAllocString(&pszAnsiColumnName, pszColumnName, 0, CP_ACP); + EseExitOnFailure(hr, "Failed converting column name to ansi"); + + jEr = JetGetTableColumnInfoA(jsSession, jtTable, pszAnsiColumnName, &jcdTempBase, sizeof(JET_COLUMNBASE), JET_ColInfoBase); + if (JET_errSuccess == jEr) + { + // Return the found columnID + if (NULL != pjcColumn) + { + *pjcColumn = jcdTempBase.columnid; + } + + ExitFunction1(hr = S_OK); + } + ExitOnJetFailure(jEr, hr, "Failed to check if column exists: %s", pszAnsiColumnName); + +LExit: + ReleaseStr(pszAnsiColumnName); + + return hr; +} + +HRESULT DAPI EseMoveCursor( + __in JET_SESID jsSession, + __in JET_TABLEID jtTable, + __in LONG lRow + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + jEr = JetMove(jsSession, jtTable, lRow, 0); + ExitOnJetFailure(jEr, hr, "Failed to move jet cursor by amount: %d", lRow); + +LExit: + return hr; +} + +HRESULT DAPI EseDeleteRow( + __in JET_SESID jsSession, + __in JET_TABLEID jtTable + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + jEr = JetDelete(jsSession, jtTable); + ExitOnJetFailure(jEr, hr, "Failed to delete row"); + +LExit: + return hr; +} + +HRESULT DAPI EseBeginTransaction( + __in JET_SESID jsSession + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + jEr = JetBeginTransaction(jsSession); + ExitOnJetFailure(jEr, hr, "Failed to begin transaction"); + +LExit: + return hr; +} + +HRESULT DAPI EseRollbackTransaction( + __in JET_SESID jsSession, + __in BOOL fAll + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + jEr = JetRollback(jsSession, fAll ? JET_bitRollbackAll : 0); + ExitOnJetFailure(jEr, hr, "Failed to rollback transaction"); + +LExit: + return hr; +} + +HRESULT DAPI EseCommitTransaction( + __in JET_SESID jsSession + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + jEr = JetCommitTransaction(jsSession, 0); + ExitOnJetFailure(jEr, hr, "Failed to commit transaction"); + +LExit: + return hr; +} + +HRESULT DAPI EsePrepareUpdate( + __in JET_SESID jsSession, + __in JET_TABLEID jtTable, + __in ULONG ulPrep + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + jEr = JetPrepareUpdate(jsSession, jtTable, ulPrep); + ExitOnJetFailure(jEr, hr, "Failed to prepare for update of type: %ul", ulPrep); + +LExit: + return hr; +} + +HRESULT DAPI EseFinishUpdate( + __in JET_SESID jsSession, + __in JET_TABLEID jtTable, + __in BOOL fSeekToInsertedRecord + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + unsigned char rgbBookmark[JET_cbBookmarkMost + 1]; + DWORD cbBookmark; + + if (fSeekToInsertedRecord) + { + jEr = JetUpdate(jsSession, jtTable, rgbBookmark, sizeof(rgbBookmark), &cbBookmark); + ExitOnJetFailure(jEr, hr, "Failed to run update and retrieve bookmark"); + + jEr = JetGotoBookmark(jsSession, jtTable, rgbBookmark, cbBookmark); + ExitOnJetFailure(jEr, hr, "Failed to seek to recently updated record using bookmark"); + } + else + { + jEr = JetUpdate(jsSession, jtTable, NULL, 0, NULL); + ExitOnJetFailure(jEr, hr, "Failed to run update (without retrieving bookmark)"); + } + +LExit: + // If we fail, the caller won't expect that the update wasn't finished, so we'll cancel their entire update to leave them in a good state + if (FAILED(hr)) + { + JetPrepareUpdate(jsSession, jtTable, JET_prepCancel); + } + + return hr; +} + +HRESULT DAPI EseSetColumnBinary( + __in JET_SESID jsSession, + __in ESE_TABLE_SCHEMA tsTable, + __in DWORD dwColumn, + __in_bcount(cbBuffer) const BYTE* pbBuffer, + __in SIZE_T cbBuffer + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + jEr = JetSetColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, pbBuffer, static_cast(cbBuffer), 0, NULL); + ExitOnJetFailure(jEr, hr, "Failed to set binary value into column of database"); + +LExit: + return hr; +} + +HRESULT DAPI EseSetColumnDword( + __in JET_SESID jsSession, + __in ESE_TABLE_SCHEMA tsTable, + __in DWORD dwColumn, + __in DWORD dwValue + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + jEr = JetSetColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, &dwValue, sizeof(DWORD), 0, NULL); + ExitOnJetFailure(jEr, hr, "Failed to set dword value into column of database: %u", dwValue); + +LExit: + return hr; +} + +HRESULT DAPI EseSetColumnBool( + __in JET_SESID jsSession, + __in ESE_TABLE_SCHEMA tsTable, + __in DWORD dwColumn, + __in BOOL fValue + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + BYTE bValue = fValue ? 0xFF : 0x00; + + jEr = JetSetColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, &bValue, 1, 0, NULL); + ExitOnJetFailure(jEr, hr, "Failed to set bool value into column of database"); + +LExit: + return hr; +} + +HRESULT DAPI EseSetColumnString( + __in JET_SESID jsSession, + __in ESE_TABLE_SCHEMA tsTable, + __in DWORD dwColumn, + __in_z LPCWSTR pwzValue + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + size_t cchValue = 0; + ULONG cbValueSize = 0; + + if (pwzValue) + { + hr = ::StringCchLengthW(pwzValue, STRSAFE_MAX_LENGTH, &cchValue); + EseExitOnRootFailure(hr, "Failed to get string length: %ls", pwzValue); + } + + cbValueSize = static_cast((cchValue + 1) * sizeof(WCHAR)); // add 1 for null character, then multiply by size of WCHAR to get bytes + + jEr = JetSetColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, pwzValue, cbValueSize, 0, NULL); + ExitOnJetFailure(jEr, hr, "Failed to set string value into column of database: %ls", pwzValue); + +LExit: + return hr; +} + +HRESULT DAPI EseSetColumnEmpty( + __in JET_SESID jsSession, + __in ESE_TABLE_SCHEMA tsTable, + __in DWORD dwColumn + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + jEr = JetSetColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, NULL, 0, 0, NULL); + ExitOnJetFailure(jEr, hr, "Failed to set empty value into column of database"); + +LExit: + return hr; +} + +HRESULT DAPI EseGetColumnBinary( + __in JET_SESID jsSession, + __in ESE_TABLE_SCHEMA tsTable, + __in DWORD dwColumn, + __deref_inout_bcount(*piBuffer) BYTE** ppbBuffer, + __inout SIZE_T* piBuffer + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + ULONG ulActualSize = 0; + + jEr = JetRetrieveColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, NULL, 0, &ulActualSize, 0, NULL); + if (JET_wrnBufferTruncated == jEr) + { + jEr = JET_errSuccess; + } + ExitOnJetFailure(jEr, hr, "Failed to check size of binary value from record"); + + if (NULL == *ppbBuffer) + { + *ppbBuffer = reinterpret_cast(MemAlloc(ulActualSize, FALSE)); + EseExitOnNull(*ppbBuffer, hr, E_OUTOFMEMORY, "Failed to allocate memory for reading binary value column"); + } + else + { + *ppbBuffer = reinterpret_cast(MemReAlloc(*ppbBuffer, ulActualSize, FALSE)); + EseExitOnNull(*ppbBuffer, hr, E_OUTOFMEMORY, "Failed to reallocate memory for reading binary value column"); + } + + jEr = JetRetrieveColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, *ppbBuffer, ulActualSize, NULL, 0, NULL); + ExitOnJetFailure(jEr, hr, "Failed to retrieve binary value from record"); + + *piBuffer = static_cast(ulActualSize); + +LExit: + if (FAILED(hr)) + { + ReleaseNullMem(*ppbBuffer); + } + + return hr; +} + +HRESULT DAPI EseGetColumnDword( + __in JET_SESID jsSession, + __in ESE_TABLE_SCHEMA tsTable, + __in DWORD dwColumn, + __out DWORD *pdwValue + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + jEr = JetRetrieveColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, pdwValue, sizeof(DWORD), NULL, 0, NULL); + ExitOnJetFailure(jEr, hr, "Failed to retrieve dword value from record"); + +LExit: + return hr; +} + +HRESULT DAPI EseGetColumnBool( + __in JET_SESID jsSession, + __in ESE_TABLE_SCHEMA tsTable, + __in DWORD dwColumn, + __out BOOL *pfValue + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + BYTE bValue = 0; + + jEr = JetRetrieveColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, &bValue, 1, NULL, 0, NULL); + ExitOnJetFailure(jEr, hr, "Failed to retrieve bool value from record"); + + if (bValue == 0) + { + *pfValue = FALSE; + } + else + { + *pfValue = TRUE; + } + +LExit: + return hr; +} + +HRESULT DAPI EseGetColumnString( + __in JET_SESID jsSession, + __in ESE_TABLE_SCHEMA tsTable, + __in DWORD dwColumn, + __out LPWSTR *ppszValue + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + ULONG ulActualSize = 0; + + jEr = JetRetrieveColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, NULL, 0, &ulActualSize, 0, NULL); + if (JET_wrnBufferTruncated == jEr) + { + jEr = JET_errSuccess; + } + ExitOnJetFailure(jEr, hr, "Failed to check size of string value from record"); + + hr = StrAlloc(ppszValue, ulActualSize); + EseExitOnFailure(hr, "Failed to allocate string while retrieving column value"); + + jEr = JetRetrieveColumn(jsSession, tsTable.jtTable, tsTable.pcsColumns[dwColumn].jcColumn, *ppszValue, ulActualSize, NULL, 0, NULL); + ExitOnJetFailure(jEr, hr, "Failed to retrieve string value from record"); + +LExit: + return hr; +} + +HRESULT DAPI EseBeginQuery( + __in JET_SESID jsSession, + __in JET_TABLEID jtTable, + __in ESE_QUERY_TYPE qtQueryType, + __out ESE_QUERY_HANDLE *peqhHandle + ) +{ + UNREFERENCED_PARAMETER(jsSession); + UNREFERENCED_PARAMETER(jtTable); + + HRESULT hr = S_OK; + + *peqhHandle = static_cast(MemAlloc(sizeof(ESE_QUERY), TRUE)); + EseExitOnNull(*peqhHandle, hr, E_OUTOFMEMORY, "Failed to allocate new query"); + + ESE_QUERY *peqHandle = static_cast(*peqhHandle); + peqHandle->qtQueryType = qtQueryType; + peqHandle->jsSession = jsSession; + peqHandle->jtTable = jtTable; + +LExit: + return hr; +} + +// Utility function used by other functions to set a query column +HRESULT DAPI SetQueryColumn( + __in ESE_QUERY_HANDLE eqhHandle, + __in_bcount(cbData) const void *pvData, + __in DWORD cbData, + __in JET_GRBIT jGrb + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + ESE_QUERY *peqHandle = static_cast(eqhHandle); + + if (peqHandle->dwColumns == countof(peqHandle->pvData)) + { + hr = E_NOTIMPL; + EseExitOnFailure(hr, "Dutil hasn't implemented support for queries of more than %d columns", countof(peqHandle->pvData)); + } + + if (0 == peqHandle->dwColumns) // If it's the first column, start a new key + { + jGrb = jGrb | JET_bitNewKey; + } + + jEr = JetMakeKey(peqHandle->jsSession, peqHandle->jtTable, pvData, cbData, jGrb); + ExitOnJetFailure(jEr, hr, "Failed to begin new query"); + + // If the query is wildcard, setup the cached copy of pvData + if (ESE_QUERY_EXACT != peqHandle->qtQueryType) + { + peqHandle->pvData[peqHandle->dwColumns] = MemAlloc(cbData, FALSE); + EseExitOnNull(peqHandle->pvData[peqHandle->dwColumns], hr, E_OUTOFMEMORY, "Failed to allocate memory"); + + memcpy(peqHandle->pvData[peqHandle->dwColumns], pvData, cbData); + + peqHandle->cbData[peqHandle->dwColumns] = cbData; + } + + // Increment the number of total columns + ++peqHandle->dwColumns; + +LExit: + return hr; +} + +HRESULT DAPI EseSetQueryColumnBinary( + __in ESE_QUERY_HANDLE eqhHandle, + __in_bcount(cbBuffer) const BYTE* pbBuffer, + __in SIZE_T cbBuffer, + __in BOOL fFinal // If this is true, all other key columns in the query will be set to "*" + ) +{ + HRESULT hr = S_OK; + ESE_QUERY *peqHandle = static_cast(eqhHandle); + JET_GRBIT jGrb = 0; + + if (cbBuffer > DWORD_MAX) + { + ExitFunction1(hr = E_INVALIDARG); + } + + if (fFinal) + { + if (ESE_QUERY_FROM_TOP == peqHandle->qtQueryType) + { + jGrb = jGrb | JET_bitFullColumnStartLimit; + } + else if (ESE_QUERY_FROM_BOTTOM == peqHandle->qtQueryType) + { + jGrb = jGrb | JET_bitFullColumnEndLimit; + } + } + + hr = SetQueryColumn(eqhHandle, reinterpret_cast(pbBuffer), static_cast(cbBuffer), jGrb); + EseExitOnFailure(hr, "Failed to set value of query colum (as binary) to:"); + +LExit: + return hr; +} + +HRESULT DAPI EseSetQueryColumnDword( + __in ESE_QUERY_HANDLE eqhHandle, + __in DWORD dwData, + __in BOOL fFinal + ) +{ + HRESULT hr = S_OK; + ESE_QUERY *peqHandle = static_cast(eqhHandle); + JET_GRBIT jGrb = 0; + + if (fFinal) + { + if (ESE_QUERY_FROM_TOP == peqHandle->qtQueryType) + { + jGrb = jGrb | JET_bitFullColumnStartLimit; + } + else if (ESE_QUERY_FROM_BOTTOM == peqHandle->qtQueryType) + { + jGrb = jGrb | JET_bitFullColumnEndLimit; + } + } + + hr = SetQueryColumn(eqhHandle, (const void *)&dwData, sizeof(DWORD), jGrb); + EseExitOnFailure(hr, "Failed to set value of query colum (as dword) to: %u", dwData); + +LExit: + return hr; +} + +HRESULT DAPI EseSetQueryColumnBool( + __in ESE_QUERY_HANDLE eqhHandle, + __in BOOL fValue, + __in BOOL fFinal + ) +{ + HRESULT hr = S_OK; + BYTE bByte = fValue ? 0xFF : 0x00; + ESE_QUERY *peqHandle = static_cast(eqhHandle); + JET_GRBIT jGrb = 0; + + if (fFinal) + { + if (ESE_QUERY_FROM_TOP == peqHandle->qtQueryType) + { + jGrb = jGrb | JET_bitFullColumnStartLimit; + } + else if (ESE_QUERY_FROM_BOTTOM == peqHandle->qtQueryType) + { + jGrb = jGrb | JET_bitFullColumnEndLimit; + } + } + + hr = SetQueryColumn(eqhHandle, (const void *)&bByte, 1, jGrb); + EseExitOnFailure(hr, "Failed to set value of query colum (as bool) to: %s", fValue ? "TRUE" : "FALSE"); + +LExit: + return hr; +} + +HRESULT DAPI EseSetQueryColumnString( + __in ESE_QUERY_HANDLE eqhHandle, + __in_z LPCWSTR pszString, + __in BOOL fFinal + ) +{ + HRESULT hr = S_OK; + DWORD dwStringSize = 0; + size_t cchString = 0; + ESE_QUERY *peqHandle = static_cast(eqhHandle); + JET_GRBIT jGrb = 0; + + if (pszString) + { + hr = ::StringCchLengthW(pszString, STRSAFE_MAX_LENGTH, &cchString); + EseExitOnRootFailure(hr, "Failed to get size of column string"); + } + + dwStringSize = static_cast(sizeof(WCHAR) * (cchString + 1)); // Add 1 for null terminator + + if (fFinal) + { + if (ESE_QUERY_FROM_TOP == peqHandle->qtQueryType) + { + jGrb = jGrb | JET_bitFullColumnStartLimit; + } + else if (ESE_QUERY_FROM_BOTTOM == peqHandle->qtQueryType) + { + jGrb = jGrb | JET_bitFullColumnEndLimit; + } + } + + hr = SetQueryColumn(eqhHandle, (const void *)pszString, dwStringSize, jGrb); + EseExitOnFailure(hr, "Failed to set value of query colum (as string) to: %ls", pszString); + +LExit: + return hr; +} + +HRESULT DAPI EseFinishQuery( + __in ESE_QUERY_HANDLE eqhHandle + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + + ESE_QUERY *peqHandle = static_cast(eqhHandle); + + if (peqHandle->fIndexRangeSet) + { + jEr = JetSetIndexRange(peqHandle->jsSession, peqHandle->jtTable, JET_bitRangeRemove); + ExitOnJetFailure(jEr, hr, "Failed to release index range"); + + peqHandle->fIndexRangeSet = FALSE; + } + + for (int i=0; i < countof(peqHandle->pvData); ++i) + { + ReleaseMem(peqHandle->pvData[i]); + } + + ReleaseMem(peqHandle); + +LExit: + return hr; +} + +HRESULT DAPI EseRunQuery( + __in ESE_QUERY_HANDLE eqhHandle + ) +{ + HRESULT hr = S_OK; + JET_ERR jEr = JET_errSuccess; + JET_GRBIT jGrb = 0; + JET_GRBIT jGrbSeekType = 0; + DWORD i; + + ESE_QUERY *peqHandle = static_cast(eqhHandle); + + if (ESE_QUERY_EXACT == peqHandle->qtQueryType) + { + jEr = JetSeek(peqHandle->jsSession, peqHandle->jtTable, JET_bitSeekEQ); + ExitOnJetFailure(jEr, hr, "Failed to seek EQ within jet table"); + } + else + { + if (ESE_QUERY_FROM_TOP == peqHandle->qtQueryType) + { + jGrbSeekType = JET_bitSeekGE; + } + else if (ESE_QUERY_FROM_BOTTOM == peqHandle->qtQueryType) + { + jGrbSeekType = JET_bitSeekLE; + } + + jEr = JetSeek(peqHandle->jsSession, peqHandle->jtTable, jGrbSeekType); + if (jEr == JET_wrnSeekNotEqual) + { + jEr = JET_errSuccess; + } + + // At this point we've already set our cursor to the beginning of the range of records to select. + // Now we'll make a key pointing to the end of the range of records to select, so we can call JetSetIndexRange() + // For a semi-explanation, see this doc page: http://msdn.microsoft.com/en-us/library/aa964799%28EXCHG.10%29.aspx + for (i = 0; i < peqHandle->dwColumns; ++i) + { + if (i == 0) + { + jGrb = JET_bitNewKey; + } + else + { + jGrb = 0; + } + + // On the last iteration + if (i == peqHandle->dwColumns - 1) + { + jGrb |= JET_bitFullColumnEndLimit; + } + + jEr = JetMakeKey(peqHandle->jsSession, peqHandle->jtTable, peqHandle->pvData[i], peqHandle->cbData[i], jGrb); + ExitOnJetFailure(jEr, hr, "Failed to begin new query"); + } + + jEr = JetSetIndexRange(peqHandle->jsSession, peqHandle->jtTable, JET_bitRangeUpperLimit); + ExitOnJetFailure(jEr, hr, "Failed to set index range"); + + peqHandle->fIndexRangeSet = TRUE; + + // Sometimes JetBlue doesn't check if there is a current record when calling the above function (and sometimes it does) + // So, let's check if there is a current record before returning (by reading the first byte of one). + jEr = JetMove(peqHandle->jsSession, peqHandle->jtTable, 0, 0); + ExitOnJetFailure(jEr, hr, "Failed to check if there is a current record after query"); + } + +LExit: + return hr; +} -- cgit v1.2.3-55-g6feb