99999久久久久久亚洲,欧美人与禽猛交狂配,高清日韩av在线影院,一个人在线高清免费观看,啦啦啦在线视频免费观看www

熱線(xiàn)電話(huà):13121318867

登錄
首頁(yè)精彩閱讀如何定制一個(gè)基于REST Service的ODBC驅(qū)動(dòng)程序_數(shù)據(jù)分析師
如何定制一個(gè)基于REST Service的ODBC驅(qū)動(dòng)程序_數(shù)據(jù)分析師
2014-12-01
收藏


如何定制一個(gè)基于REST Service的ODBC驅(qū)動(dòng)程序



REST Service能夠幫助開(kāi)發(fā)者以簡(jiǎn)單統(tǒng)一的接口向終端用戶(hù)提供服務(wù)。然而數(shù)據(jù)分析的應(yīng)用場(chǎng)景中,一些成熟的數(shù)據(jù)分析工具(例如Tableau, Excel等)要求用戶(hù)提供ODBC數(shù)據(jù)源,在這種情況下,REST Service并不能滿(mǎn)足用戶(hù)所有對(duì)數(shù)據(jù)的使用需求。本文從實(shí)現(xiàn)的角度詳細(xì)介紹了如何在現(xiàn)有REST Service的基礎(chǔ)上,完成一個(gè)定制ODBC驅(qū)動(dòng)程序的開(kāi)發(fā)。文章側(cè)重介紹了ODBC驅(qū)動(dòng)程序的實(shí)現(xiàn)原理,結(jié)合代碼詳細(xì)說(shuō)明了ODBC與REST Service之間的數(shù)據(jù)交互,并在文章末尾介紹了ODBC客戶(hù)端程序調(diào)用ODBC API的原理,以及實(shí)際開(kāi)發(fā)中調(diào)試環(huán)境的搭建。

  可能受益的讀者

  目前主流的數(shù)據(jù)分析工具,例如Tableau,Microstrategy,excel都只能夠ODBC Driver,來(lái)訪(fǎng)問(wèn)底層的數(shù)據(jù)源。也就是說(shuō),在開(kāi)發(fā)數(shù)據(jù)庫(kù)或者數(shù)據(jù)倉(cāng)庫(kù)的過(guò)程中,即使我們已經(jīng)實(shí)現(xiàn)了符合SQL規(guī)范的數(shù)據(jù)訪(fǎng)問(wèn)接口,哪怕提供了自己的JDBC驅(qū)動(dòng)程序,仍然無(wú)法保證數(shù)據(jù)用戶(hù)能夠有效地使用我們的數(shù)據(jù)。為此,我們需要額外地為數(shù)據(jù)源定制一個(gè)ODBC Driver。

  如果你的數(shù)據(jù)源恰好是類(lèi)似MongoDB,Hbase這樣的常見(jiàn)數(shù)據(jù)庫(kù)產(chǎn)品,你或許可以考慮直接從購(gòu)買(mǎi)一些商業(yè)產(chǎn)品,例如Simba ODBC Driver來(lái)一勞永逸地解決你的需求,但是將意味著不小的開(kāi)支。更難辦的情況是你的數(shù)據(jù)源并不是那么主流,還沒(méi)有任何可以直接購(gòu)買(mǎi)的驅(qū)動(dòng)程序可以適用于它,那么定制一個(gè)自己的ODBC Driver可能是你最好的選擇。即使你是一個(gè)對(duì)ODBC Driver一無(wú)所知的開(kāi)發(fā)者,本文也將給你帶來(lái)或多或少的幫助。

  我們的處境

  簡(jiǎn)單地說(shuō),我們團(tuán)隊(duì)用java開(kāi)發(fā)了一個(gè)特別的SQL引擎。在項(xiàng)目初期我們只有JDBC驅(qū)動(dòng)程序,還有一個(gè)用于服務(wù)于網(wǎng)頁(yè)客戶(hù)端的REST Server,但是我們沒(méi)有ODBC 驅(qū)動(dòng),因此大多數(shù)的客戶(hù)并不能真正地使用我們的產(chǎn)品完成他們地工作。

  為了解決這個(gè)問(wèn)題,我們?cè)O(shè)計(jì)了如下圖的解決方案:我們使用REST Server統(tǒng)一地接受來(lái)自所有客戶(hù)端的請(qǐng)求,包括網(wǎng)頁(yè)客戶(hù)端和使用ODBC Driver的客戶(hù)端。REST Server中使用JDBC驅(qū)動(dòng)來(lái)訪(fǎng)問(wèn)我們的數(shù)據(jù)庫(kù)。當(dāng)然如果你的客戶(hù)端就是一個(gè)java程序,你完全可以直接通過(guò)JDBC來(lái)訪(fǎng)問(wèn)我們的數(shù)據(jù)庫(kù),從而節(jié)省這些步驟帶來(lái)的開(kāi)銷(xiāo)。這張圖片中并未展示這種情況。

  在客戶(hù)端,我們深度定制了一個(gè)專(zhuān)有的ODBC Driver,它向上層的應(yīng)用程序提供了標(biāo)準(zhǔn)的ODBC API,封裝所有實(shí)現(xiàn)的邏輯。在底層實(shí)現(xiàn)上,它調(diào)用C++的REST庫(kù),將應(yīng)用程序發(fā)送過(guò)來(lái)的SQL查詢(xún)請(qǐng)求封裝成REST請(qǐng)求,發(fā)送給我們的REST Server,并在得到結(jié)果后,再以符合ODBC規(guī)范的方式,返回給上層的應(yīng)用程序。


 

  從Hello World開(kāi)始

  對(duì)于從來(lái)沒(méi)有接觸過(guò)ODBC的開(kāi)發(fā)者來(lái)說(shuō),了解一個(gè)ODBC客戶(hù)端的行為有助于理解定制一個(gè)ODBC驅(qū)動(dòng)需要實(shí)現(xiàn)哪些具體的API。下圖中展示了一個(gè)簡(jiǎn)單的ODBC客戶(hù)端程序的實(shí)現(xiàn),每一行代碼都配有詳細(xì)的注釋解釋它的行為,通讀代碼,不難擁有一個(gè)直觀的理解。為了簡(jiǎn)化代碼,我們省略了所有錯(cuò)誤檢查的代碼。所有的SQLXXX格式的函數(shù),都是ODBC定義的標(biāo)準(zhǔn)API。

  我們將這段程序分成了五塊區(qū)域,分別標(biāo)記為A~E。A區(qū)域和B區(qū)域依次初始化了三個(gè)與ODBC相關(guān)的句柄,分別是:

  Environment handle (hEnv):包含一個(gè)或者多個(gè)Connection handle。同時(shí),一些全局的信息也包含在內(nèi),例如客戶(hù)端所需要的ODBC版本,以及環(huán)境級(jí)別的診斷信息。

  Connection handle (hConn):代表了一個(gè)對(duì)DBMS/數(shù)據(jù)源的連接,包含了連接級(jí)別的信息,例如連接的超時(shí)時(shí)間,隔離級(jí)別,以及連接級(jí)別的診斷信息。

  Statement handle (hStmt):可以將它看做是某個(gè)具體的查詢(xún)請(qǐng)求,例如 SELECT * FROM employee。

  值得一提的是ODBC規(guī)范只定義了數(shù)據(jù)源以何種方式暴露數(shù)據(jù)訪(fǎng)問(wèn)的接口,但是并沒(méi)有規(guī)定如何實(shí)現(xiàn),這也包括三類(lèi)句柄的具體實(shí)現(xiàn)。事實(shí)上,在代碼中這三類(lèi)句柄都通過(guò)SQLHANDLE類(lèi)型來(lái)傳遞,而SQLHANDLE本質(zhì)上是一個(gè)void *類(lèi)型,指向我們自定義的相應(yīng)的結(jié)構(gòu)體。

  ODBC為應(yīng)用程序提供了一系列的C語(yǔ)言風(fēng)格的API來(lái)支持訪(fǎng)問(wèn)查詢(xún)。不同于面向?qū)ο笳Z(yǔ)言的驅(qū)動(dòng)程序,使用ODBC驅(qū)動(dòng)程序的應(yīng)用程序需要為將要返回的數(shù)據(jù)提前準(zhǔn)備好內(nèi)存區(qū)域,從這個(gè)角度說(shuō),ODBC的任務(wù)是正確地將用戶(hù)需要的數(shù)據(jù),搬運(yùn)到用戶(hù)指定的內(nèi)存區(qū)域之中(可能帶有一些數(shù)據(jù)轉(zhuǎn)化,例如如果應(yīng)用程序需要支持Unicode,那么ODBC Driver可能需要將char類(lèi)型的源數(shù)據(jù)轉(zhuǎn)化為wchar類(lèi)型)。下圖的A區(qū)域中初始化了一系列的句柄和變量,其中第305~307行就在程序的棧上開(kāi)辟了這樣一些用作緩存的內(nèi)存區(qū)域。事實(shí)上,在E區(qū)域,我們傳入了變量x和i的引用,因此我們可以把第308~309行的兩個(gè)數(shù)值變量也看作是這樣存儲(chǔ)返回結(jié)果的內(nèi)存區(qū)域。


 

  在區(qū)域C中,我們調(diào)用SQLDriverConnect函數(shù),同時(shí)傳入hConn句柄和連接數(shù)據(jù)源所需要的用戶(hù)名,密碼,驅(qū)動(dòng)名稱(chēng)等信息。我們?cè)贠DBC Driver的實(shí)現(xiàn)中,完成對(duì)hConn的一系列賦值操作(其初始化操作已經(jīng)在B區(qū)域中完成),使得hConn成為一個(gè)可用的連接句柄。當(dāng)SQLDriverConnect的返回值等于SQL_SUCCESS的時(shí)候,一個(gè)DBMS/數(shù)據(jù)源連接就正式被建立好了。

  在區(qū)域D中,客戶(hù)端程序首先對(duì)Statement句柄hStmt進(jìn)行了初始化,然后直接使用SQLExecDirect API進(jìn)行查詢(xún)。該API的第二個(gè)參數(shù)接收的字符串,即為查詢(xún)請(qǐng)求的SQL。

  在最后的E區(qū)域中,客戶(hù)端獲取查詢(xún)結(jié)果。在這段程序中,客戶(hù)端首先在第331行利用SQLColAttribute接口提取了第一列的列名屬性,其中第二個(gè)參數(shù)指定返回結(jié)果的第一類(lèi),第三個(gè)常量參數(shù)SQL_DESC_NAME指定所需要的屬性標(biāo)志(列名)。接下來(lái),客戶(hù)端利用SQLBindCol接口,告知ODBC Driver它希望將第一列的返回結(jié)果填入到szColData所指向這段內(nèi)存中,并且用常量參數(shù)SQL_C_TCHAR告知ODBC客戶(hù)端希望看到的返回類(lèi)型是char數(shù)據(jù)類(lèi)型,這個(gè)過(guò)程被稱(chēng)作綁定(Bind)。一切就緒之后,客戶(hù)端調(diào)用SQLFetch接口獲取結(jié)果第一行的第一列,由于沒(méi)有綁定其他的返回列,因此SQLFetch實(shí)際上只會(huì)返回第一列的內(nèi)容。

  在一個(gè)更加現(xiàn)實(shí)的客戶(hù)端代碼中,可能會(huì)首先調(diào)用SQLNumResultCols接口得知返回結(jié)果總共有多少列。對(duì)于每一個(gè)返回的列,客戶(hù)端使用比SQLColAttribute接口更加便捷的SQLDescribeCol接口,一次性獲取該列的所有基本信息,包括列名,類(lèi)型,長(zhǎng)度等信息。根據(jù)返回的列信息,客戶(hù)端有針對(duì)性地調(diào)整SQLBindCol的參數(shù),以便正確地接受相應(yīng)的返回結(jié)果。一切就緒之后,客戶(hù)端調(diào)用SQLFetch接口,得到需要的查詢(xún)結(jié)果。由于每次調(diào)用SQLFetch返回結(jié)果集中的一行,客戶(hù)端程序需要重復(fù)調(diào)用SQLFetch,直到SQLFetch不再返回SQL_SUCCESS,而是返回SQL_NO_DATA,表示已經(jīng)不再有更多的行可以返回。客戶(hù)端可以根據(jù)需求的不同,考慮就究竟是復(fù)用同一塊內(nèi)存空間來(lái)接受結(jié)果中不同的行(取一行,使用一行),還是在一開(kāi)始就申請(qǐng)能夠容納所有行的大塊內(nèi)存,每次綁定傳入不同的內(nèi)存位置(取完所有行后再一起使用結(jié)果數(shù)據(jù))。

  開(kāi)始定制ODBC Driver

  通過(guò)上一節(jié)對(duì)ODBC Driver所需要提供的接口有一定了解后,如果我們需要從無(wú)到有地寫(xiě)出一個(gè)完整的ODBC Driver,我們需要完成兩項(xiàng)工作:

  1. 實(shí)現(xiàn)客戶(hù)端所需要的所有API,MSDN給出了ODBC規(guī)范中每一個(gè)API的詳細(xì)定義(http://msdn.microsoft.com/en-us/library/ms714562(v=vs.85).aspx, 慶幸的是我們沒(méi)必要實(shí)現(xiàn)每一個(gè)接口,只需要根據(jù)客戶(hù)端的行為找到最小的必要集),在Windows中,我們將所有的API的實(shí)現(xiàn)打包成為一個(gè)可執(zhí)行模塊,通常是一個(gè)dll文件。

  2. 讓程序客戶(hù)端程序能夠正確地找到我們的Driver,簡(jiǎn)而言之,我們需要正確地將ODBC Driver安裝到客戶(hù)端程序運(yùn)行的機(jī)器上。

  由于在實(shí)現(xiàn)復(fù)雜度上,第二步明顯低于第一步,另外對(duì)第二步的介紹也有助于我們能夠?qū)DBC Driver有整體性的理解。因此雖然第二步事實(shí)上依賴(lài)于第一步的完成,我們?nèi)匀粌?yōu)先介紹第二步的實(shí)現(xiàn)。在這里我們可以假設(shè)我們已經(jīng)實(shí)現(xiàn)了所有必須的API,這些API的實(shí)現(xiàn)都被包裝在一個(gè)名為driver.dll的文件中。

  第一步:安裝ODBC Driver

  理解ODBC架構(gòu)

  在ODBC架構(gòu)中( http://msdn.microsoft.com/en-us/library/aa266933(v=vs.60).aspx),有四個(gè)關(guān)鍵的模塊,分別是:

  API:通過(guò)調(diào)用ODBC的接口來(lái)連接數(shù)據(jù)源,發(fā)送和接受數(shù)據(jù),以及關(guān)閉連接。這里的API僅僅是接口,并沒(méi)有實(shí)現(xiàn),具體的實(shí)現(xiàn)需要在Driver模塊中完成。

  Driver Manager:向應(yīng)用程序提供諸如可用的數(shù)據(jù)源的信息,按需動(dòng)態(tài)加載驅(qū)動(dòng)程序,提供參數(shù)檢查等。

  Driver:處理ODBC的函數(shù)方法,管理應(yīng)用程序和特定的DBMS/數(shù)據(jù)源之間的所有交互。如果有必要,Driver還會(huì)將標(biāo)準(zhǔn)SQL格式的請(qǐng)求語(yǔ)句轉(zhuǎn)為目標(biāo)數(shù)據(jù)源的原生SQL格式。

  Data Source:由數(shù)據(jù)及其數(shù)據(jù)庫(kù)引擎組成。


 

  其中API和Driver Manager已經(jīng)一般是操作系統(tǒng)自帶的。在Window上,我們可以通過(guò)安裝MDAC (Microsoft Data Access Components, http://www.microsoft.com/en-us/download/details.aspx?id=5793),來(lái)獲得所有所需的頭文件已經(jīng)相關(guān)的工具資源。在Unix環(huán)境中,也有類(lèi)似的UnixODBC。在下文中我們僅考慮Windows下的ODBC開(kāi)發(fā)。在開(kāi)發(fā)ODBC Driver的時(shí)候,底層的數(shù)據(jù)源一般也已經(jīng)就緒。因此我們僅需要將ODBC Driver注冊(cè)到Driver Manager之中。

  注冊(cè)O(shè)DBC Driver

  Driver Manager通過(guò)注冊(cè)表得知所有可用的ODBC Driver的列表,以及它們各自的詳細(xì)信息。具體位置在(假設(shè)目標(biāo)機(jī)器安裝了64位Windows):

  32位 驅(qū)動(dòng):

  HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\ODBC\ODBCINST.INI\ODBC Drivers

  64位 驅(qū)動(dòng):

  HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI\ODBC Drivers

  以32位Windows為例,我們打開(kāi)注冊(cè)表中的 ODBC Drivers鍵,可以看到系統(tǒng)中所有安裝的32位ODBC的驅(qū)動(dòng)程序都在其中,我們將自己的ODBC起名為ebayODBCDriver,并在ODBC Driver中加入相應(yīng)的一行:


 

  在得知ODBC Driver的名字之后,Driver Manager會(huì)在ODBC Driver的父親節(jié)點(diǎn)上,也就是ODBCINST.INI中尋找相應(yīng)的ODBC Driver的詳細(xì)信息。必備的信息包括Driver屬性和Setup屬性,分別告訴ODBC Manager在哪里尋找Driver和Setup的可執(zhí)行程序。其中Driver對(duì)應(yīng)于我們將要實(shí)現(xiàn)的ODBC Driver,而Setup程序中包括了一些設(shè)置DSN的時(shí)候用到的API,根據(jù)通常的慣例,這部分的API也和ODBC Driver的API一同編譯在同一個(gè)dll文件中,因此我們看到在ebayODBCDriver下,Driver和Setup指向同一個(gè)dll文件。在此也可以定義一些其他的屬性,但這些都是可選的。


 

  一切就緒,我們就能夠在Control Panel--Administrator Tools--Data Sources(ODBC) 中為我們的ODBD Driver創(chuàng)建DSN了。對(duì)于32位的ODBC Driver而言,我們需要使用C:\Windows\SysWOW64\odbcad32.exe這個(gè)32位版本的Data Sources(ODBC)。值得一提的是在使用Data Sources(ODBC)創(chuàng)建DSN的過(guò)程中,我們使用到了上文提及的Setup程序中的接口,尤其是ConfigDSN接口。( http://msdn.microsoft.com/en-us/library/ms709275(v=vs.85).aspx )

  為了簡(jiǎn)化這些安裝步驟,我們可以以Windows Installer的形式,包裝所有這些注冊(cè)O(shè)DBC Driver的邏輯,讓使用者可以簡(jiǎn)單地通過(guò)安裝一個(gè)exe,完成ODBC Driver的安裝和注冊(cè)。


 

  第二步:實(shí)現(xiàn)ODBC API

  Descriptors

  在MSDN對(duì)ODBC架構(gòu)的闡述中,ODBC Driver模塊的核心職能在于管理應(yīng)用程序和特定的DBMS/數(shù)據(jù)源之間的所有交互。交互的載體在于數(shù)據(jù),而有數(shù)據(jù)就意味著需要內(nèi)存空間對(duì)其進(jìn)行存儲(chǔ)。前文已經(jīng)提到,ODBC Driver本身定位于一個(gè)數(shù)據(jù)的搬運(yùn)工,它將應(yīng)用程序的請(qǐng)求轉(zhuǎn)交給數(shù)據(jù)源,并且將數(shù)據(jù)源返回的數(shù)據(jù)結(jié)果逐行搬運(yùn)給應(yīng)用程序。在這個(gè)過(guò)程中ODBC Driver需要至少兩塊內(nèi)存區(qū)域,或者簡(jiǎn)稱(chēng)buffer:一塊用來(lái)緩存從數(shù)據(jù)源返回的結(jié)果,另外一塊用來(lái)緩存移交給應(yīng)用程序的結(jié)果。這兩塊buffer不僅包含數(shù)據(jù)本身,還包括對(duì)數(shù)據(jù)的描述。例如在返回給應(yīng)用程序的數(shù)據(jù)中,ODBC Driver不僅需要維護(hù)列數(shù)據(jù)本身,還維護(hù)了該列數(shù)據(jù)的類(lèi)型,長(zhǎng)度等信息,在ODBC Driver和應(yīng)用程序之間,這些數(shù)據(jù)和信息統(tǒng)稱(chēng)為Application Row Buffer Descriptor(ARD)。相應(yīng)地,數(shù)據(jù)源交給ODBC Driver的也不僅僅是數(shù)據(jù)本身,還包括對(duì)每一個(gè)返回的列的元信息描述,這部分信息和數(shù)據(jù)統(tǒng)稱(chēng)為Implementation Row Buffer Descriptor(IRD)。


 

  事實(shí)上ARD中保存數(shù)據(jù)本身的內(nèi)存區(qū)域,是由應(yīng)用程序在調(diào)用SQLBindCol的時(shí)候傳入的,ARD并不負(fù)責(zé)這段內(nèi)存的申請(qǐng)和釋放,而存放其他信息所需要的內(nèi)存則由ODBC Driver負(fù)責(zé)維護(hù)。當(dāng)應(yīng)用程序調(diào)用例如SQLNumResultCols,SQLColAttribute,SQLDescribeCol等接口的時(shí)候,ODBC Driver找到ARD中相應(yīng)的內(nèi)容,返回給調(diào)用者;當(dāng)應(yīng)用程序調(diào)用SQLBindCol和SQLFetch的時(shí)候,ODBC Driver通過(guò)ARD得知該返回?cái)?shù)據(jù)應(yīng)該被存放的位置(指針),從IRD中讀取最新的一行數(shù)據(jù),施加一些必要的據(jù)類(lèi)型轉(zhuǎn)化,將其搬運(yùn)到指定位置。

  與ARD,IRD對(duì)應(yīng)的,ODBC標(biāo)準(zhǔn)還提供了另外兩種buffer, 分別是Application parameter descriptor (APD) 和Implementation parameter descriptor (IPD),用來(lái)處理動(dòng)態(tài)查詢(xún)中的參數(shù),本文中對(duì)這兩類(lèi)buffer不做詳細(xì)介紹。這四類(lèi)buffer構(gòu)成了ODBC世界中的四個(gè)最主要的Descriptor。更多信息,讀者可以參考(http://msdn.microsoft.com/en-us/library/ms716262(v=vs.85).aspx )。

  在具體的實(shí)現(xiàn)中,ARD和IRD被定義為特殊的結(jié)構(gòu)體(struct),存放與代表Statement的結(jié)構(gòu)體GenODBCStmt(Generic ODBC Statement)之中。下圖展示了我們一個(gè)GenODBCStmt結(jié)構(gòu)體的部分成員:首先是標(biāo)識(shí)其類(lèi)型的標(biāo)簽(區(qū)分與代表Environment和代表Connection的結(jié)構(gòu)體),然后是前文提到的四種不同用途的descriptor,接著是Statement級(jí)別的一些屬性信息,SQL語(yǔ)句等等。


 

  我們以ARD為例詳細(xì)分析,ARD的具體實(shí)現(xiàn)不受ODBC規(guī)范的約束,可以自由實(shí)現(xiàn)。在我們的實(shí)現(xiàn)中,我們用結(jié)構(gòu)體GENODBCARD代表一個(gè)Statement的所對(duì)應(yīng)的ARD。每個(gè)ARD包括了一些所有返回列共享的信息,又包含了不同返回列的不同的詳細(xì)信息,用更細(xì)力度的結(jié)構(gòu)體GenODBCARDItem來(lái)代表。


 

  觀察GenODBCARDItem中的成員變量,很容易和ODBC API產(chǎn)生一一對(duì)應(yīng)的關(guān)系。例如這里的DataConciseType對(duì)應(yīng)與SQLColAttribute返回的列類(lèi)型信息,而DataPtr成員則對(duì)應(yīng)SQLBindCol所傳入的內(nèi)存空間的指針??偠灾蠖鄶?shù)ODBC API的實(shí)現(xiàn),本質(zhì)上就是對(duì)ARD和IRD的不同成員變量的訪(fǎng)問(wèn)和修改。


 

  利用REST API訪(fǎng)問(wèn)數(shù)據(jù)源

  ARD負(fù)責(zé)ODBC Driver與應(yīng)用程序之間的交互,其初始化在應(yīng)用程序調(diào)用SQLBindCol的過(guò)程中完成。而IRD負(fù)責(zé)ODBC Driver與數(shù)據(jù)源之間的交互,其初始化需要在和數(shù)據(jù)源進(jìn)行數(shù)據(jù)交換的的過(guò)程中完成。

  我們首先定義REST請(qǐng)求的接口:

  std::unique_ptr restQuery(

  wchar_t* rawSql, char* serverAddr, char* username, char* passwd);

  SQLResponse類(lèi)封裝一個(gè)SQL請(qǐng)求所有的返回內(nèi)容,對(duì)于每個(gè)SQL查詢(xún),REST Server 返回一個(gè)SQLResponse的實(shí)例。該實(shí)例中的columnMetas成員包含了每一個(gè)返回列的信息,而results成員則以字符串形式保存了返回結(jié)果的每一行。


 

  ODBC Driver將返回的SQLResponse實(shí)例交給該Statement的IRD,這樣IRD在事實(shí)上擁有了該SQL查詢(xún)所有的返回結(jié)果。當(dāng)諸如SQLFetch的API被調(diào)用的時(shí)候,ODBC Driver只需要找到IRD中的的SQLResponse實(shí)例,對(duì)其中的信息進(jìn)行解析,即可返回調(diào)用者需要的信息。


 

  其他實(shí)現(xiàn)

  SQLTables,SQLColumns的實(shí)現(xiàn)

  應(yīng)用程序調(diào)用這兩個(gè)API來(lái)獲得當(dāng)前連接所能查詢(xún)的所有表和列。由于這部分信息是整個(gè)數(shù)據(jù)庫(kù)連接過(guò)程中公用的,我們?cè)趹?yīng)用程序SQLDriverConnect,建立連接句柄的時(shí)候,就向REST Server發(fā)送獲得所有表和列元信息的請(qǐng)求。類(lèi)似于SQLResponse,我們用MetadataResponse封裝這些信息,并將該返回的實(shí)例交給代表連接句柄的GenODBCConn結(jié)構(gòu)體負(fù)責(zé)維護(hù)。當(dāng)SQLTable,SQLColumns的請(qǐng)求到來(lái)時(shí),ODBC Driver從GenODBCConn的MetadataResponse中抽取需要的信息返回給調(diào)用者。

  SQLDriverConnect的實(shí)現(xiàn)

  檢查傳入的連接字符串,如果其中明確地給出了連接數(shù)據(jù)庫(kù)所需的地址,用戶(hù)名,密碼等信息,則直接用REST請(qǐng)求的方式確認(rèn)REST Server存活,并且獲得表和列得元信息MetadataResponse。如果上述信息不完整,則向應(yīng)用程程序彈出對(duì)話(huà)框補(bǔ)全這些信息。當(dāng)然,調(diào)用者也可以直接在連接字符串中指定已經(jīng)配置好的DSN,直接完成連接。

  SQLGetInfo的實(shí)現(xiàn)

  當(dāng)應(yīng)用程序無(wú)法通過(guò)ODBC Driver的名稱(chēng)確認(rèn)ODBC Driver的來(lái)源時(shí),它將調(diào)用一系列的SQLGetInfo接口來(lái)獲得該ODBC Driver的一些特性,例如版本,支持的函數(shù),支持的數(shù)據(jù)類(lèi)型等。Tableau等BI工具會(huì)根據(jù)這些返回結(jié)果來(lái)選擇不同的行為,例如Tableau在生成SQL查詢(xún)的時(shí)候無(wú)法確定表名等標(biāo)志符該用單引號(hào)還是雙引號(hào)來(lái)包圍,它就會(huì)調(diào)用SQLGetInfo來(lái)獲得SQL_IDENTIFIER_QUOTE_CHAR 屬性來(lái)確定。ODBC Driver實(shí)現(xiàn)的過(guò)程中需要通過(guò)對(duì)應(yīng)用程序行為的分析,確定SQLGetInfo該返回的結(jié)果。

  Unicode的支持

  從ODBC 3.5開(kāi)始,ODBC同時(shí)支持UNICODE和ANSI編碼的API。ODBC使用后綴W來(lái)代表支持UNICODE的接口,例如SQLDriverConnect和SQLDriverConnectW。詳情可以參考(http://msdn.microsoft.com/en-us/library/ms716246(v=vs.85).aspx )。

  客戶(hù)端原理及診斷

  客戶(hù)端原理

  在Visual Studio中創(chuàng)建一個(gè)標(biāo)準(zhǔn)的ODBC客戶(hù)端程序需要:

  引入頭文件,對(duì)該文件的引用會(huì)間接引用, , 等頭文件,在這些頭文件中包含了ODBC標(biāo)準(zhǔn)中的各種常量和方法的聲明,例如SQLDriverConnect方法和SQL_SUCCESS, SQL_ERROR常量等。

  在項(xiàng)目的Linker中,加入對(duì)odbc32.lib和odbccpp32.lib的依賴(lài)。在Visual Studio 2012中,新創(chuàng)建的C++項(xiàng)目默認(rèn)就對(duì)這兩個(gè)靜態(tài)鏈接庫(kù)依賴(lài),如下圖所示:


 

  這兩部所需的頭文件資源和靜態(tài)鏈接庫(kù)資源都由ODBC Manager(或者說(shuō)Windows操作系統(tǒng))提供。在這兩步完成之后,我們就可以像在文章開(kāi)始的客戶(hù)端示例程序中那樣,任意地調(diào)用ODBC的API??蛻?hù)端程序能夠成功地訪(fǎng)問(wèn)對(duì)應(yīng)的ODBC驅(qū)動(dòng)程序中的相應(yīng)的API,歸功于ODBC Manager在中間的幫助。我們可以注意到在compile和link期間,客戶(hù)端程序并不與我們的ODBC驅(qū)動(dòng)程序產(chǎn)生任何依賴(lài),相反,客戶(hù)端程序只依賴(lài)于ODBC Manager提供的頭文件和靜態(tài)鏈接庫(kù)。其中的調(diào)用原理如下圖紫色箭頭所示:


 

  ODBC Manager在odbc32.lib和odbccpp32.lib中給出了所有ODBC API的“實(shí)現(xiàn)”,這樣客戶(hù)端才能在不依賴(lài)于任何具體ODBC Driver的前提下完成link。然而這種“實(shí)現(xiàn)”事實(shí)上只是一種單純的轉(zhuǎn)發(fā),ODBC Manger根據(jù)客戶(hù)端程序中指定的驅(qū)動(dòng)程序名稱(chēng),將客戶(hù)端的ODBC API請(qǐng)求轉(zhuǎn)發(fā)到相應(yīng)的ODBC驅(qū)動(dòng)程序中。

  以客戶(hù)端調(diào)用SQLDriverConnect 為例,由于客戶(hù)端程序和odbc32.lib被link在了一起,因此程序會(huì)首先進(jìn)入odbc32.lib中對(duì)SQLDriverConnect的實(shí)現(xiàn)之中,該實(shí)現(xiàn)通過(guò)explicit linking(可以理解為運(yùn)行時(shí)動(dòng)態(tài)的link,參考 http://msdn.microsoft.com/en-us/library/784bt7z7.aspx),可以找到并調(diào)用我們真實(shí)的ODBC驅(qū)動(dòng)程序的DLL(簡(jiǎn)稱(chēng)driver.dll)中的SQLDriverConnect方法,于是最終客戶(hù)端程序調(diào)用到了driver.dll中的SQLDriverConnect方法。

  ODBC客戶(hù)端程序,ODBC Manager,ODBC Driver這三者這樣的角色分配可以?xún)?yōu)雅地完成客戶(hù)端程序和具體驅(qū)動(dòng)程序之間的解耦,保證了我們可以在系統(tǒng)中自由地增加和指定新的ODBC驅(qū)動(dòng)程序。然而這樣的設(shè)計(jì)也給開(kāi)發(fā)和調(diào)試帶來(lái)了麻煩:我們無(wú)法簡(jiǎn)單地在Visual Studio中以Debug模式追溯客戶(hù)端調(diào)用過(guò)程中程序運(yùn)行的每一步,因?yàn)榭蛻?hù)端并不對(duì)我們的驅(qū)動(dòng)程序產(chǎn)生顯示的依賴(lài),所以Visual Studio無(wú)法為我們找到驅(qū)動(dòng)程序?qū)?yīng)的源代碼。

  診斷客戶(hù)端程序

  解決方法是取消Visual Studio中的C++項(xiàng)目對(duì)ODBC Manger提供的靜態(tài)鏈接庫(kù)的依賴(lài),轉(zhuǎn)而直接依賴(lài)自定制ODBC驅(qū)動(dòng)程序的靜態(tài)鏈接庫(kù)(簡(jiǎn)稱(chēng)driver.lib,該靜態(tài)鏈接庫(kù)會(huì)在我們編譯driver.dll的時(shí)候作為副產(chǎn)品同時(shí)產(chǎn)生)。在Property Pages->Configuration Properties->Linker->Additional Dependencies中,我們首先取消”Inherit from parent or project defaults”選項(xiàng),然后將”Inherited Values”中除了odbc32.lib和odbccpp32.lib以外的值拷入Additional Dependencies。這樣保證了我們不失去其它的項(xiàng)目默認(rèn)依賴(lài),同時(shí)也去除了客戶(hù)端對(duì)ODBC Manager的odbc32.lib和odbccpp32.lib的依賴(lài)。最后,我們?cè)诖思尤雂river.lib的路徑,使客戶(hù)端程序顯示地依賴(lài)我們的ODBC驅(qū)動(dòng)程序的實(shí)現(xiàn)。


 

  值得一提的是我們?nèi)匀恍枰妙^文件,在Visual Studio看來(lái),唯一的不同之處是,頭文件中的ODBC API的實(shí)現(xiàn)不再在odbc32.lib和odbccpp32.lib之中,而是在我們的驅(qū)動(dòng)程序driver.lib之中了。Visual Studio可以根據(jù)這種認(rèn)識(shí)自動(dòng)定位到驅(qū)動(dòng)程序的源代碼,于是我們就可以在Visual Studio的Debug模式中自由地追溯程序間的函數(shù)調(diào)用了。

  其他Debug 工具

  ODBC Driver的Debug相對(duì)繁瑣,除了將一些關(guān)鍵步驟打印到日志文件,這里還推薦另外兩種方式進(jìn)行補(bǔ)充:

  1. 使用ODBC Data Source Administrator的Tracing功能捕捉ODBC Manager的日志。


 

  2. Include windows.h, 使用OutputDebugString方法輸出debug語(yǔ)句,配合dbmon觀察程序輸出。該方式可以在log日志無(wú)法工作的時(shí)候作為補(bǔ)充,而且在dbmon沒(méi)有開(kāi)啟的情況下,不會(huì)對(duì)系統(tǒng)造成額外的開(kāi)銷(xiāo)。具體參見(jiàn)http://msdn.microsoft.com/en-us/library/windows/desktop/aa363362(v=vs.85).aspx 。

  總結(jié)

  本文詳細(xì)描述了如何在已有REST服務(wù)的前提下,完成一個(gè)以REST API作為后臺(tái)數(shù)據(jù)訪(fǎng)問(wèn)方式的ODBC驅(qū)動(dòng)程序。本文工作能夠幫助REST服務(wù)的提供團(tuán)隊(duì)以O(shè)DBC的形式包裝其服務(wù),使得他們的用戶(hù)能夠在更多的商用平臺(tái)上消費(fèi)他們的服務(wù)。CDA數(shù)據(jù)分析師培訓(xùn)官網(wǎng)


數(shù)據(jù)分析咨詢(xún)請(qǐng)掃描二維碼

若不方便掃碼,搜微信號(hào):CDAshujufenxi

數(shù)據(jù)分析師資訊
更多

OK
客服在線(xiàn)
立即咨詢(xún)
客服在線(xiàn)
立即咨詢(xún)
') } function initGt() { var handler = function (captchaObj) { captchaObj.appendTo('#captcha'); captchaObj.onReady(function () { $("#wait").hide(); }).onSuccess(function(){ $('.getcheckcode').removeClass('dis'); $('.getcheckcode').trigger('click'); }); window.captchaObj = captchaObj; }; $('#captcha').show(); $.ajax({ url: "/login/gtstart?t=" + (new Date()).getTime(), // 加隨機(jī)數(shù)防止緩存 type: "get", dataType: "json", success: function (data) { $('#text').hide(); $('#wait').show(); // 調(diào)用 initGeetest 進(jìn)行初始化 // 參數(shù)1:配置參數(shù) // 參數(shù)2:回調(diào),回調(diào)的第一個(gè)參數(shù)驗(yàn)證碼對(duì)象,之后可以使用它調(diào)用相應(yīng)的接口 initGeetest({ // 以下 4 個(gè)配置參數(shù)為必須,不能缺少 gt: data.gt, challenge: data.challenge, offline: !data.success, // 表示用戶(hù)后臺(tái)檢測(cè)極驗(yàn)服務(wù)器是否宕機(jī) new_captcha: data.new_captcha, // 用于宕機(jī)時(shí)表示是新驗(yàn)證碼的宕機(jī) product: "float", // 產(chǎn)品形式,包括:float,popup width: "280px", https: true // 更多配置參數(shù)說(shuō)明請(qǐng)參見(jiàn):http://docs.geetest.com/install/client/web-front/ }, handler); } }); } function codeCutdown() { if(_wait == 0){ //倒計(jì)時(shí)完成 $(".getcheckcode").removeClass('dis').html("重新獲取"); }else{ $(".getcheckcode").addClass('dis').html("重新獲取("+_wait+"s)"); _wait--; setTimeout(function () { codeCutdown(); },1000); } } function inputValidate(ele,telInput) { var oInput = ele; var inputVal = oInput.val(); var oType = ele.attr('data-type'); var oEtag = $('#etag').val(); var oErr = oInput.closest('.form_box').next('.err_txt'); var empTxt = '請(qǐng)輸入'+oInput.attr('placeholder')+'!'; var errTxt = '請(qǐng)輸入正確的'+oInput.attr('placeholder')+'!'; var pattern; if(inputVal==""){ if(!telInput){ errFun(oErr,empTxt); } return false; }else { switch (oType){ case 'login_mobile': pattern = /^1[3456789]\d{9}$/; if(inputVal.length==11) { $.ajax({ url: '/login/checkmobile', type: "post", dataType: "json", data: { mobile: inputVal, etag: oEtag, page_ur: window.location.href, page_referer: document.referrer }, success: function (data) { } }); } break; case 'login_yzm': pattern = /^\d{6}$/; break; } if(oType=='login_mobile'){ } if(!!validateFun(pattern,inputVal)){ errFun(oErr,'') if(telInput){ $('.getcheckcode').removeClass('dis'); } }else { if(!telInput) { errFun(oErr, errTxt); }else { $('.getcheckcode').addClass('dis'); } return false; } } return true; } function errFun(obj,msg) { obj.html(msg); if(msg==''){ $('.login_submit').removeClass('dis'); }else { $('.login_submit').addClass('dis'); } } function validateFun(pat,val) { return pat.test(val); }