// 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; }