allegdb.h 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. /*-------------------------------------------------------------------------
  2. allegdb.h
  3. Allegiance database layer (the OLE DB one, as opposed to the ODBC one, which also exists)
  4. Owner:
  5. Copyright 1986-2000 Microsoft Corporation, All Rights Reserved
  6. *-----------------------------------------------------------------------*/
  7. #ifndef _ALLEGDB_H_
  8. #define _ALLEGDB_H_
  9. #include <oledb.h>
  10. #include <sqloledb.h>
  11. #include <atldbcli.h>
  12. #include <..\test\TCLib\TCThread.h>
  13. class CSQLCore;
  14. class CSQLQuery;
  15. class CSQLThread;
  16. class CSQLQueueThread;
  17. typedef Mlist_utl<CSQLQuery*> MListQueries;
  18. typedef Mlink_utl<CSQLQuery*> MLinkQueries;
  19. typedef Slist_utl<CSQLQuery*> SListQueries;
  20. typedef Slink_utl<CSQLQuery*> SLinkQueries;
  21. extern unsigned CALLBACK SQLThreadProc(void * pvsqlThread);
  22. extern unsigned CALLBACK SQLQueueProc(void * pvSQLQueueThread);
  23. class ISQLSite2 : public IObject
  24. {
  25. public:
  26. virtual void OnSQLErrorRecord(SSERRORINFO * perror, OLECHAR * postrError) {};
  27. virtual void OnOLEDBErrorRecord(BSTR bstrDescription, GUID guid, DWORD dwHelpContext,
  28. BSTR bstrHelpFile, BSTR bstrSource ) {};
  29. };
  30. /*-------------------------------------------------------------------------
  31. * CSQLCore
  32. *-------------------------------------------------------------------------
  33. Keeper of everything sql. Maintains two sql euery queues, one for
  34. queries that require a response, and one for queries that don't
  35. */
  36. class CSQLCore
  37. {
  38. public:
  39. CSQLCore(ISQLSite2 * psqlsite) :
  40. m_cSilentThreads(0),
  41. m_cNotifyThreads(0),
  42. m_hKillSQLEvent(0),
  43. m_pthdQueue(NULL),
  44. m_hQueryNotify(NULL),
  45. m_hQuerySilent(NULL),
  46. m_nThreadIDNotify(0),
  47. m_psqlsite(psqlsite)
  48. {}
  49. ~CSQLCore();
  50. HRESULT Init(LPCOLESTR strSQLConfig, DWORD nThreadIDNotify, DWORD cSilentThreads, DWORD cNotifyThreads);
  51. void PostQuery(CSQLQuery * pquery);
  52. DWORD GetCountSilentThreads()
  53. {
  54. return m_cSilentThreads;
  55. }
  56. DWORD GetCountNotifyThreads()
  57. {
  58. return m_cNotifyThreads;
  59. }
  60. const CDataSource & GetDataSource()
  61. {
  62. return m_connection;
  63. }
  64. HANDLE GetNotifySemaphore()
  65. {
  66. return m_hQueryNotify;
  67. }
  68. HANDLE GetSilentSemaphore()
  69. {
  70. return m_hQuerySilent;
  71. }
  72. CSQLQuery * GetNotifyQuery()
  73. {
  74. m_listQueriesNotify.lock();
  75. MLinkQueries * pl = m_listQueriesNotify.first();
  76. CSQLQuery * pquery = pl->data();
  77. pl->unlink();
  78. m_listQueriesNotify.unlock();
  79. return pquery;
  80. }
  81. CSQLQuery * GetSilentQuery()
  82. {
  83. m_listQueriesSilent.lock();
  84. MLinkQueries * pl = m_listQueriesSilent.first();
  85. CSQLQuery * pquery = pl->data();
  86. pl->unlink();
  87. m_listQueriesSilent.unlock();
  88. return pquery;
  89. }
  90. const DWORD GetNotifyThreadID()
  91. {
  92. return m_nThreadIDNotify;
  93. }
  94. void DumpErrorInfo(IUnknown * punk, IID iid, bool * pfRetry)
  95. {
  96. m_cs.Lock(); // don't think this is thread safe, and since it's only called on errors, it doesn't affect performance
  97. CDBErrorInfo errorInfo;
  98. ULONG cRecords;
  99. HRESULT hr;
  100. ULONG iError;
  101. CComBSTR bstrDesc, bstrHelpFile, bstrSource;
  102. GUID guid;
  103. DWORD dwHelpContext;
  104. USES_CONVERSION;
  105. TRef<ISQLServerErrorInfo> psqlError;
  106. IMalloc * pMalloc;
  107. if (pfRetry)
  108. *pfRetry = false;
  109. hr = CoGetMalloc(1, &pMalloc);
  110. ZSucceeded(hr);
  111. LCID lcLocale = GetSystemDefaultLCID();
  112. hr = errorInfo.GetErrorRecords(punk, iid, &cRecords);
  113. if (SUCCEEDED(hr) && errorInfo.m_spErrorInfo)
  114. {
  115. for (iError = 0; iError < cRecords; iError++)
  116. {
  117. hr = errorInfo.GetAllErrorInfo(iError, lcLocale, &bstrDesc, &bstrSource, &guid, &dwHelpContext, &bstrHelpFile);
  118. ZSucceeded(hr);
  119. hr = errorInfo.GetCustomErrorObject(iError, IID_ISQLServerErrorInfo, (IUnknown**) &psqlError);
  120. SSERRORINFO * pssError = NULL;
  121. OLECHAR * postrError = NULL;
  122. if (SUCCEEDED(hr)) // sql specific info--in which case we don't care about the ole db error, which just contains less info
  123. {
  124. hr = psqlError->GetErrorInfo(&pssError, &postrError);
  125. if (SUCCEEDED(hr) && pssError && postrError) // I've seen these not get set--don't know why
  126. {
  127. m_psqlsite->OnSQLErrorRecord(pssError, postrError);
  128. if (1205 == pssError->lNative || // deadlocked transaction
  129. 7312 == pssError->lNative) // timeout
  130. {
  131. assert (pfRetry); // they better be prepared to retry
  132. *pfRetry = true;
  133. }
  134. }
  135. }
  136. if (FAILED(hr) || (!pssError && !postrError))
  137. {
  138. m_psqlsite->OnOLEDBErrorRecord(bstrDesc, guid, dwHelpContext, bstrHelpFile, bstrSource);
  139. }
  140. bstrSource.Empty();
  141. bstrDesc.Empty();
  142. bstrHelpFile.Empty();
  143. if (pssError)
  144. pMalloc->Free(pssError);
  145. if (postrError)
  146. pMalloc->Free(postrError);
  147. }
  148. }
  149. else
  150. {
  151. GUID guid;
  152. ZeroMemory(&guid, sizeof(guid));
  153. m_psqlsite->OnOLEDBErrorRecord(L"errorInfo.GetErrorRecords failed, which is bad (and unexpected).", guid, 0, L"", L"");
  154. Sleep(1000); // give the debug output thread a chance to write the error to the file
  155. *(DWORD*)0 = 0; // we can't assert from any thread, so let's just hard break
  156. }
  157. if (!(pfRetry && *pfRetry)) // let's take a look at any database errors we get, since we don't handle them gracefully
  158. {
  159. Sleep(1000); // give the debug output thread a chance to write the error to the file
  160. *(DWORD*)0 = 0; // we can't assert from any thread, so let's just hard break
  161. debugf("!!DBERR: retry not attempted !!!!\n");
  162. }
  163. m_cs.Unlock();
  164. return;
  165. }
  166. private:
  167. friend class CSQLQueueThread;
  168. void AddQuery(CSQLQuery * pquery);
  169. HANDLE m_hKillSQLEvent;
  170. HANDLE m_hQueryNotify; // signaled when there's a query available that requires notification
  171. HANDLE m_hQuerySilent; // signaled when there's a query available that DOESN'T require notification
  172. CSQLQueueThread * m_pthdQueue;
  173. CSQLThread ** m_pargSilentThreads;
  174. CSQLThread ** m_pargNotifyThreads;
  175. DWORD m_cSilentThreads;
  176. DWORD m_cNotifyThreads;
  177. ZString m_strSQLConfig;
  178. CDataSource m_connection;
  179. MListQueries m_listQueriesNotify; // queries that need a reply
  180. MListQueries m_listQueriesSilent; // queries that are send and forget
  181. DWORD m_nThreadIDNotify; // thread to send completion notification to
  182. ZAutoCriticalSection m_cs;
  183. TRef<ISQLSite2> m_psqlsite;
  184. };
  185. /*-------------------------------------------------------------------------
  186. * CSQLQuery
  187. *-------------------------------------------------------------------------
  188. Base class that all queries must derive from
  189. These are instantiated in two different way for two different purposes.
  190. The user can create these, fill in the data, and post CSQLCore::PostQuery() them.
  191. They are also created by the CSQLThread's to cache the prepared version in each
  192. thread for performance reasons. The instance the users creates is NEVER the actual
  193. instance used to execute the query. It's always a copy.
  194. */
  195. class CSQLQuery
  196. {
  197. public:
  198. CSQLQuery()
  199. {}
  200. virtual ~CSQLQuery() {};
  201. // Mandatory Overrides
  202. virtual HRESULT OnExecute() = 0;
  203. virtual HRESULT OnPrepare(const CSession & session) = 0;
  204. virtual CSQLQuery * Copy(CSQLQuery * pqueryDest) = 0; // pqueryDest is either existing obj to copy into, or NULL for creation of new object
  205. virtual REFGUID GetGuid() = 0;
  206. virtual TCHAR * GetStrQuery() = 0;
  207. virtual void DataReady() = 0; // this happens in the main thread
  208. virtual bool GetNotify() = 0;
  209. virtual IUnknown * GetIUnknown() = 0;
  210. virtual bool GetCallbackOnMainThread() const = 0;
  211. };
  212. /*-------------------------------------------------------------------------
  213. * CSQLThread
  214. *-------------------------------------------------------------------------
  215. Waits for posted queries to appear in one of the two query queues,
  216. depending on which type of thread this is. Each thread will ONLY
  217. pull either queries that do, or do not require notification.
  218. The derived query thread is responsible for actually executing the
  219. query and getting data. A completion message is posted to the main
  220. thread to synchronize access to database results.
  221. */
  222. class CSQLThread
  223. {
  224. public:
  225. CSQLThread(HANDLE hEventDie, bool fNotify, CSQLCore * psql) :
  226. m_hEventDie(hEventDie),
  227. m_fNotify(fNotify),
  228. m_psql(psql)
  229. {
  230. m_pThread = TCThread::BeginMsgThread(SQLThreadProc, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
  231. // TODO: if that fails?
  232. m_pThread->ResumeThread();
  233. }
  234. CSQLCore * GetSQLCore()
  235. {
  236. return m_psql;
  237. }
  238. HRESULT ServiceQuery(CSQLQuery * pquery);
  239. bool GetNotify()
  240. {
  241. return m_fNotify;
  242. }
  243. HRESULT Open()
  244. {
  245. HRESULT hr = m_session.Open(m_psql->GetDataSource());
  246. if (FAILED(hr))
  247. m_psql->DumpErrorInfo(m_session.m_spOpenRowset, IID_IOpenRowset, NULL);
  248. return hr;
  249. }
  250. HANDLE GetEventDie()
  251. {
  252. return m_hEventDie;
  253. }
  254. DWORD GetThreadID()
  255. {
  256. return m_pThread->m_nThreadID;
  257. }
  258. private:
  259. TCThread * m_pThread;
  260. HANDLE m_hEventDie;
  261. CSession m_session;
  262. bool m_fNotify; // whether this thread services queries that require notification
  263. SListQueries m_listQueries; // these are the UNIQUE queries (by guid) that this thread has seen (and prepared) before, added on demand
  264. CSQLCore * m_psql;
  265. };
  266. /*-------------------------------------------------------------------------
  267. * CSQLQueueThread
  268. *-------------------------------------------------------------------------
  269. Manages taking posted and putting them in the right queue
  270. */
  271. class CSQLQueueThread
  272. {
  273. public:
  274. CSQLQueueThread(HANDLE hEventDie, CSQLCore * psql) :
  275. m_hEventDie(hEventDie),
  276. m_psql(psql)
  277. {
  278. m_pThread = TCThread::BeginMsgThread(SQLQueueProc, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
  279. // TODO: if that fails?
  280. m_pThread->ResumeThread();
  281. }
  282. void AddQuery(CSQLQuery * pquery)
  283. {
  284. m_psql->AddQuery(pquery);
  285. }
  286. HANDLE GetEventDie()
  287. {
  288. return m_hEventDie;
  289. }
  290. CSQLCore * GetSQLCore()
  291. {
  292. return m_psql;
  293. }
  294. DWORD GetThreadID()
  295. {
  296. return m_pThread->m_nThreadID;
  297. }
  298. private:
  299. TCThread * m_pThread;
  300. HANDLE m_hEventDie;
  301. CSQLCore * m_psql;
  302. };
  303. #define WM_SQL (WM_APP + 0x2000) // somewhat random, hopefully safe, range
  304. #define wm_sql_addquery WM_SQL // Sent from outside in. LPARAM: CSQLQuery*, WPARAM: unused
  305. #define wm_sql_querydone WM_SQL + 1 // Sent inside out. LPARAM: CSQLQuery*, WPARAM: unused
  306. /*-------------------------------------------------------------------------
  307. * CQuery
  308. *-------------------------------------------------------------------------
  309. Templatized on the data structure (with column and/or parameter maps)
  310. used to shuttle data to/from the database
  311. */
  312. template <class TQueryData, bool TfResultSet>
  313. class CQuery : public CSQLQuery
  314. {
  315. public:
  316. CQuery(void (pfDataReady)(CQuery*)) :
  317. m_pargQueryData(NULL),
  318. m_cRows(0),
  319. m_cRowsAlloc(0),
  320. m_fCallbackOnMainThread(true)
  321. {
  322. m_pfDataReady = pfDataReady;
  323. if (GUID_NULL == s_guid)
  324. CoCreateGuid(&s_guid);
  325. }
  326. ~CQuery()
  327. {
  328. ClearRows();
  329. }
  330. virtual REFGUID GetGuid()
  331. {
  332. return s_guid;
  333. }
  334. virtual void DataReady() // this happens in the main thread
  335. {
  336. if (m_pfDataReady)
  337. (*m_pfDataReady)(this);
  338. delete this;
  339. }
  340. virtual HRESULT OnExecute()
  341. {
  342. ClearRows();
  343. HRESULT hr = m_cmd.Open();
  344. if (FAILED(hr))
  345. return hr;
  346. if (TfResultSet)
  347. {
  348. // ok, because sql is the way it is, we can't know how many rows we're gonna get until we get 'em,
  349. // so it's either traverse (and copy) all the rows twice, or allocate as you go. I choose the latter.
  350. while (S_OK == (hr = m_cmd.MoveNext())) // end of rows is also success
  351. {
  352. m_cRows++;
  353. if (m_cRows > m_cRowsAlloc)
  354. {
  355. m_cRowsAlloc = m_cRows * 2;
  356. if (m_pargQueryData)
  357. m_pargQueryData = (TQueryData *) HeapReAlloc(GetProcessHeap(), 0, m_pargQueryData, sizeof(TQueryData) * m_cRowsAlloc);
  358. else
  359. m_pargQueryData = (TQueryData *) HeapAlloc(GetProcessHeap(), 0, sizeof(TQueryData) * m_cRowsAlloc);
  360. }
  361. m_pargQueryData[m_cRows - 1] = *GetData();
  362. }
  363. }
  364. if (SUCCEEDED(hr))
  365. m_cmd.Close();
  366. return hr;
  367. }
  368. virtual HRESULT OnPrepare(const CSession & session)
  369. {
  370. HRESULT hr = m_cmd.Create(session, GetStrQuery(), DBGUID_DBSQL);
  371. if (SUCCEEDED(hr))
  372. hr = m_cmd.Prepare(100000); // what's the right number for "a lot"?
  373. return hr;
  374. }
  375. virtual CSQLQuery * Copy(CSQLQuery * pqueryDest) // pqueryDest is either existing obj to copy into, or NULL for creation of new object
  376. {
  377. if (!pqueryDest)
  378. pqueryDest = new CQuery(m_pfDataReady);
  379. REFGUID guid1 = pqueryDest->GetGuid();
  380. REFGUID guid2 = GetGuid();
  381. assert(guid1 == guid2); // we know it's the right type, so we can upcast
  382. // and then make a memory copy of just the user contents
  383. // NOT the size of the whole m_cmd, since we can't stomp on atl data members--atl also doesn't support copying accessors
  384. CQuery * pqueryDestT = static_cast<CQuery *>(pqueryDest);
  385. CopyMemory(&pqueryDestT->m_cmd, &m_cmd, sizeof(TQueryData));
  386. pqueryDestT->m_cRowsAlloc = m_cRowsAlloc;
  387. pqueryDestT->m_pargQueryData = m_pargQueryData;
  388. pqueryDestT->m_cRows = m_cRows;
  389. pqueryDestT->m_fCallbackOnMainThread = m_fCallbackOnMainThread;
  390. // only the copy owns the data now.
  391. m_pargQueryData = NULL;
  392. m_cRows = 0;
  393. m_cRowsAlloc = 0;
  394. m_fCallbackOnMainThread = true;
  395. return pqueryDest;
  396. }
  397. TQueryData * GetData()
  398. {
  399. return &m_cmd;
  400. }
  401. TQueryData * GetOutputRows(int * pcRows)
  402. {
  403. if (pcRows)
  404. *pcRows = (int) m_cRows;
  405. return m_pargQueryData;
  406. }
  407. virtual TCHAR * GetStrQuery()
  408. {
  409. return m_cmd.GetStrQuery();
  410. }
  411. void ClearRows()
  412. {
  413. assert(IFF(m_pargQueryData, m_cRowsAlloc > 0));
  414. if (m_pargQueryData)
  415. {
  416. HeapFree(GetProcessHeap(), 0, m_pargQueryData);
  417. m_pargQueryData = NULL;
  418. m_cRows = 0;
  419. m_cRowsAlloc = 0;
  420. }
  421. }
  422. bool GetNotify()
  423. {
  424. return !!m_pfDataReady;
  425. }
  426. IUnknown * GetIUnknown()
  427. {
  428. return m_cmd.m_spCommand;
  429. }
  430. void SetCallbackOnMainThread(bool fCallbackOnMainThread)
  431. {
  432. m_fCallbackOnMainThread = fCallbackOnMainThread;
  433. }
  434. virtual bool GetCallbackOnMainThread() const
  435. {
  436. return m_fCallbackOnMainThread;
  437. }
  438. CCommand<CAccessor<TQueryData> > m_cmd;
  439. TQueryData * m_pargQueryData;
  440. ULONG m_cRows;
  441. ULONG m_cRowsAlloc;
  442. void (*m_pfDataReady)(CQuery* pquery);
  443. bool m_fCallbackOnMainThread;
  444. public: // just so the macro can easily set this
  445. static GUID s_guid;
  446. };
  447. // ------------- MACROS USED FOR CREATING QUERIES -----------------
  448. // The queries get memmove'd, so NO objects that require special construction or external storage!
  449. // N = Name of query class, typically CQ...
  450. // R = Whether query generates any rowsets to be scanned
  451. // Q = Query command text
  452. #define BEGIN_QUERY(N, R, Q) \
  453. struct N##Data \
  454. { \
  455. TCHAR * GetStrQuery() \
  456. { \
  457. return TEXT(Q); \
  458. }
  459. #define END_QUERY(N, R) \
  460. }; \
  461. typedef class CQuery<N##Data, R> N;
  462. #endif