<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # 第二十一章:數據庫的使用 網上論壇、播客抓取器(podcatchers)甚至備份程序通常都會使用數據庫進行持久化儲存。基于 SQL 的數據庫非常常見:這種數據庫具有速度快、伸縮性好、可以通過網絡進行操作等優點,它們通常會負責處理加鎖和事務,有些數據庫甚至還提供了故障恢復(failover)功能以提高應用程序的冗余性(redundancy)。市面上的數據庫有很多不同的種類:既有 Oracle 這樣大型的商業數據庫,也有 PostgreSQL 、 MySQL 這樣的開源引擎,甚至還有 Sqlite 這樣的可嵌入引擎。 因為數據庫是如此的重要,所以 Haskell 也必須對數據庫進行支持。本章將介紹其中一個與數據庫進行互動的 Haskell 框架,并使用這個框架去構建一個播客下載器(podcast downloader),本書的 23 章還會對這個博客下載器做進一步的擴展。 ## HDBC 簡介 數據庫引擎位于數據庫棧(stack)的最底層,引擎負責將數據實際地儲存到硬盤里面,常見的數據庫引擎有 PostgreSQL 、 MySQL 和 Oracle 。 大多數現代化的數據庫引擎都支持 SQL ,也即是結構化查詢語言(Structured Query Language),并將這種語言用作讀取和寫入關系式數據庫的標準方式。不過本書并不會提供 SQL 或者關系式數據庫管理方面的教程[[49]](#)。 | [[49]](#) | O'Reilly 出版的《Learning SQL and SQL in a Nutshell》對于沒有 SQL 經驗的讀者來說可能會有所幫助。 | |-----|-----| 在擁有了支持 SQL 的數據庫引擎之后,用戶還需要尋找一種方法與引擎進行通信。雖然每個數據庫都有自己的獨有協議,但是因為各個數據庫處理的 SQL 幾乎都是相同的,所以通過為不同的協議提供不同的驅動,以此來創建一個通用的接口是完全可以做到的。 Haskell 有幾種不同的數據庫框架可用,其中某些框架在其他框架的基礎上提供了更高層次的抽象,而本章將對 HDBC —— 也即是 Haskell DataBase Connectivity 系統進行介紹。通過 HDBC ,用戶可以在只需進行少量修改甚至無需進行修改的情況下,訪問儲存在任意 SQL 數據庫里面的數據[[50]](#)。即使你并不需要更換底層的數據引擎,由多個驅動構成的 HDBC 系統也使得你在單個接口上面有更多選擇可用。 | [[50]](#) | 假設你只能使用標準的 SQL 。 | |-----|-----| HSQL 是 Haskell 的另一個數據庫抽象庫,它與 HDBC 具有相似的構想。除此之外,Haskell 還有一個名為 HaskellDB 的高層次框架,這個框架可以運行在 HDBC 或是 HSQL 之上,它被設計于用來為程序員隔離處理 SQL 時的相關細節。因為 HaskellDB 的設計無法處理一些非常常見的數據庫訪問模式,所以它并未被廣泛引用。最后,Takusen 是一個使用左折疊(left fold)方式從數據庫里面讀取數據的框架。 ## 安裝 HDBC 和驅動 為了使用 HDBC 去連給定的數據庫,用戶至少需要用到兩個包:一個包是 HDBC 的通用接口,而另一個包則是針對給定數據庫的驅動。HDBC 包和所有其他驅動都可以通過 [Hackage](http://hackage.haskell.org/) [http://hackage.haskell.org/][[51]](#)獲得,本章將使用 1.1.3 版本的 HDBC 作為示例。 | [[51]](#) | 想要了解更多關于安裝 Haskell 軟件的相關信息,請閱讀本書的《安裝 Haskell 軟件》一節。 | |-----|-----| 除了 HDBC 包之外,用戶還需要準備數據庫后端和數據庫驅動。本章會使用 Sqlite 3 作為數據庫后端,這個數據庫是一個嵌入式數據庫,因此它不需要獨立的服務器,并且也非常容易設置。很多操作系統本身就內置了 Sqlite 3 ,如果你的系統里面沒有提供這一數據庫,那么你可以到 [http://www.sqlite.org/](http://www.sqlite.org/) 里面進行下載。HDBC 的主頁上面列出了指向已有 HDBC 后端驅動的鏈接,針對 Sqlite 3 的驅動也可以通過 Hackage 下載到。 如果讀者打算使用 HDBC 去處理其他數據庫,那么可以在 [http://software.complete.org/hdbc/wiki/KnownDrivers](http://software.complete.org/hdbc/wiki/KnownDrivers) 查看 HDBC 已有的驅動:上面展示的 ODBC 綁定(binding)基本上可以讓你在任何平臺(Windows、POSIX等等)上面連接任何數據庫;針對 PostgreSQL 的綁定也是存在的;而 MySQL 同樣可以通過 ODBC 綁定進行支持,具體的信息可以在 [HDBC-ODBC API 文檔](http://software.complete.org/static/hdbc-odbc/doc/HDBC-odbc/) [http://software.complete.org/static/hdbc-odbc/doc/HDBC-odbc/]里面找到。 ## 連接數據庫 連接至數據庫需要用到數據庫后端驅動提供的連接函數。每個數據庫都有自己獨特的連接方法。用戶通常只會在初始化連接的時候直接調用從后端驅動模塊載入的函數。 數據庫連接函數會返回一個數據庫連接,不同驅動的數據庫連接類型可能并不相同,但它們總是 IConnection 類型類的一個實例,并且所有數據庫操作函數都能夠與這種類型的實例進行協作。 在完成了與數據庫的通信指揮,用戶只要調用 disconnect 函數就可以斷開與數據庫的連接。以下代碼展示了怎樣去連接一個 Sqlite 數據庫: ~~~ ghci> :module Database.HDBC Database.HDBC.Sqlite3 ghci> conn <- connectSqlite3 "test1.db" Loading package array-0.1.0.0 ... linking ... done. Loading package containers-0.1.0.1 ... linking ... done. Loading package bytestring-0.9.0.1 ... linking ... done. Loading package old-locale-1.0.0.0 ... linking ... done. Loading package old-time-1.0.0.0 ... linking ... done. Loading package mtl-1.1.0.0 ... linking ... done. Loading package HDBC-1.1.5 ... linking ... done. Loading package HDBC-sqlite3-1.1.4.0 ... linking ... done. ghci> :type conn conn :: Connection ghci> disconnect conn ~~~ ## 事務 大部分現代化 SQL 數據庫都具有事務的概念。事務可以確保一項修改的所有組成部分都會被實現,又或者全部都不實現。更進一步來說,事務可以避免訪問相同數據庫的多個進程看見正在進行的修改動作所產生的不完整數據。 大多數數據庫都要求用戶通過顯式的提交操作來將所有修改儲存到硬盤上面,又或者在“自動提交”模式下運行:這種模式在每一條語句的后面都會進行一次隱式的提交。“自動提交”模式可能會給不熟悉事務數據庫的程序員帶來一些方便,但它對于那些真正想要執行多條語句事務的人來說卻是一個阻礙。 HDBC 有意地不對自動提交模式進行支持。當用戶在修改數據庫的數據之后,它必須顯式地將修改提交到硬盤上面。有兩種方法可以在 HDBC 里面做到這件事:第一種方法就是在準備好將數據寫入到硬盤的時候,調用 commit 函數;而另一種方法則是將修改數據的代碼包裹到 withTransaction 函數里面。withTransaction 會在被包裹的函數成功執行之后自動執行提交操作。 在將數據寫入到數據庫里面的時候,可能會出現問題。也許是因為數據庫出錯了,又或者數據庫發現正在提交的數據出現了問題。在這種情況下,用戶可以“回滾”事務進行的修改:回滾動作會撤銷最近一次提交或是最近一次回滾之后發生的所有修改。在 HDBC 里面,你可以通過 rollback 函數來進行回滾。如果你使用 withTransaction 函數來包裹事務,那么函數將在事務發生異常時自動進行回滾。 要記住,回滾操作只會撤銷掉最近一次 commit 函數、 rollback 函數或者 withTransaction 函數引發的修改。數據庫并不會像版本控制系統那樣記錄全部歷史信息。本章稍后將展示一些 commit 函數的使用示例。 ## 簡單的查詢示例 最簡單的 SQL 查詢語句都是一些不返回任何數據的語句,這些查詢可以用于創建表、插入數據、刪除數據、又或者設置數據庫的參數。 run 函數是向數據庫發送查詢的最基本的函數,這個函數接受三個參數,它們分別是一個 IConnection 實例、一個表示查詢的 String 以及一個由列表組成的參數。以下代碼展示了如何使用這個函數去將一些數據儲存到數據庫里面。 ~~~ ghci> :module Database.HDBC Database.HDBC.Sqlite3 ghci> conn <- connectSqlite3 "test1.db" Loading package array-0.1.0.0 ... linking ... done. Loading package containers-0.1.0.1 ... linking ... done. Loading package bytestring-0.9.0.1 ... linking ... done. Loading package old-locale-1.0.0.0 ... linking ... done. Loading package old-time-1.0.0.0 ... linking ... done. Loading package mtl-1.1.0.0 ... linking ... done. Loading package HDBC-1.1.5 ... linking ... done. Loading package HDBC-sqlite3-1.1.4.0 ... linking ... done. ghci> run conn "CREATE TABLE test (id INTEGER NOT NULL, desc VARCHAR(80))" [] 0 ghci> run conn "INSERT INTO test (id) VALUES (0)" [] 1 ghci> commit conn ghci> disconnect conn ~~~ 在連接到數據庫之后,程序首先創建了一個名為 test 的表,接著向表里面插入了一個行。最后,程序將修改提交到數據庫,并斷開與數據庫的連接。記住,如果程序不調用 commit 函數,那么修改將不會被寫入到數據庫里面。 run 函數返回因為查詢語句而被修改的行數量。在上面展示的代碼里面,第一個查詢只是創建一個表,它并沒有修改任何行;而第二個查詢則向表里面插入了一個行,因此 run 函數返回了數字 1 。 ## SqlValue 在繼續討論后續內容之前,我們需要先了解一種由 HDBC 引入的數據類型:SqlValue 。因為 Haskell 和 SQL 都是強類型系統,所以 HDBC 會嘗試盡可能地保留類型信息。與此同時,Haskell 和 SQL 類型并不是一一對應的。更進一步來說,日期和字符串里面的特殊字符這樣的東西,在每個數據庫里面的表示方法都是不相同的。 SqlValue 類型具有 SqlString 、 SqlBool 、 SqlNull 、 SqlInteger 等多個構造器,用戶可以通過使用這些構造器,在傳給數據庫的參數列表里面表示各式各樣不同類型的數據,并且仍然能夠將這些數據儲存到一個列表里面。除此之外,SqlValue 還提供了 toSql 和 fromSql 這樣的常用函數。如果你非常關心數據的精確表示的話,那么你還是可以在有需要的時候,手動地構造 SqlValue 數據。 ## 查詢參數 HDBC 和其他數據庫一樣,都支持可替換的查詢參數。使用可替換參數主要有幾個好處:它可以預防 SQL 注射攻擊、避免因為輸入里面包含特殊字符而導致的問題、提升重復執行相似查詢時的性能、并通過查詢語句實現簡單且可移植的數據插入操作。 假設我們想要將上千個行插入到新的表 test 里面,那么我們可能會執行像 INSERTINTOtestVALUES(0,'zero') 和 INSERTINTOtestVALUES(1,'one') 這樣的查詢上千次,這使得數據庫必須獨立地分析每條 SQL 語句。但如果我們將被插入的兩個值替換為占位符,那么服務器只需要對 SQL 查詢進行一次分析,然后就可以通過重復地執行這個查詢來處理不同的數據了。 使用可替換參數的第二個原因和特殊字符有關。因為 SQL 使用單引號表示域(field)的末尾,所以如果我們想要插入字符串 "Idon'tlike1" ,那么大多數 SQL 數據庫都會要求我們把這個字符串寫成 Idon''tlike1' ,并且不同的特殊字符(比如反斜杠符號)在不同的數據庫里面也會需要不同的轉移規則。但是只要使用 HDBC ,它就會幫你自動完成所有轉義動作,以下展示的代碼就是一個例子: ~~~ ghci> conn <- connectSqlite3 "test1.db" ghci> run conn "INSERT INTO test VALUES (?, ?)" [toSql 0, toSql "zero"] 1 ghci> commit conn ghci> disconnect conn ~~~ 在這個示例里面,INSERT 查詢包含的問號是一個占位符,而跟在占位符后面的就是要傳遞給占位符的各個參數。因為 run 函數的第三個參數接受的是 SqlValue 組成的列表,所以我們使用了 toSql 去將列表中的值轉換為 SqlValue 。HDBC 會根據目前使用的數據庫,自動地將 String"zero" 轉換為正確的表示方式。 在插入大量數據的時候,可替換參數實際上并不會帶來任何性能上的提升。因此,我們需要對創建 SQL 查詢的過程做進一步的控制,具體的方法在接下來的一節里面就會進行討論。 Note 使用可替換參數 當服務器期望在查詢語句的指定部分看見一個值的時候,用戶才能使用可替換參數:比如在執行 SELECT 語句的 WHERE 子句時就可以使用可替換參數;又或者在執行 INSERT 語句的時候就可以把要插入的值設置為可替換參數;但執行 run"SELECT*from?"[toSql"tablename"] 是無法運行的。這是因為表的名字并非一個值,所以大多數數據庫都不允許這種語法。因為在實際中很少人會使用這種方式去替換一個不是值的事物,所以這并不會帶來什么大的問題。 ## 預備語句 HDBC 定義了一個 prepare 函數,它可以預先準備好一個 SQL 查詢,但是并不將查詢語句跟具體的參數綁定。prepare 函數返回一個 Statement 值來表示已編譯的查詢。 在擁有了 Statement 值之后,用戶就可以對它調用一次或多次 execute 函數。在對一個會返回數據的查詢執行 execute 函數之后,用戶可以使用任意的獲取函數去取得查詢所得的數據。諸如 run 和 quickQuery' 這樣的函數都會在內部使用查詢語句和 execute 函數;為了讓用戶可以更快捷妥當地執行常見的任務,像是 run 和 quickQuery' 這樣的函數都會在內部使用 Statement 值和 execute 函數。當用戶需要對查詢的具體執行過程有更多的控制時,就可以考慮使用 Statement 而不是 run 函數。 以下代碼展示了如何通過 Statement 值,在只使用一條查詢的情況下插入多個值: ~~~ ghci> conn <- connectSqlite3 "test1.db" ghci> stmt <- prepare conn "INSERT INTO test VALUES (?, ?)" ghci> execute stmt [toSql 1, toSql "one"] 1 ghci> execute stmt [toSql 2, toSql "two"] 1 ghci> execute stmt [toSql 3, toSql "three"] 1 ghci> execute stmt [toSql 4, SqlNull] 1 ghci> commit conn ghci> disconnect conn ~~~ 在這段代碼里面,我們創建了一個預備語句并使用 stmt 函數去調用它。我們一共執行了那個語句四次,每次都向它傳遞了不同的參數,這些參數會被用于替換原有查詢字符串中的問號。在代碼的最后,我們提交了修改并斷開數據庫。 為了方便地重復執行同一個預備語句,HDBC 還提供了 executeMany 函數,這個函數接受一個由多個數據行組成的列表作為參數,而列表中的數據行就是需要調用預備語句的數據行。正如以下代碼所示: ~~~ ghci> conn <- connectSqlite3 "test1.db" ghci> stmt <- prepare conn "INSERT INTO test VALUES (?, ?)" ghci> executeMany stmt [[toSql 5, toSql "five's nice"], [toSql 6, SqlNull]] ghci> commit conn ghci> disconnect conn ~~~ Note 更高效的查詢執行方法 在服務器上面,大多數數據庫都會對 executeMany 函數進行優化,使得查詢字符串只會被編譯一次而不是多次。[[52]](#)在一次插入大量數據的時候,這種優化可以帶來極為有效的性能提升。有些數據庫還可以將這種優化應用到執行查詢語句上面,并并非所有數據庫都能做到這一點。 | [[52]](#) | 對于不支持這一優化的數據庫,HDBC 會通過模擬這一行為來為用戶提供一致的 API ,以便執行重復的查詢。 | |-----|-----| ## 讀取結果 本章在前面已經介紹過如何通過查詢語句,將數據插入到數據庫;在接下來的內容中,我們將學習從數據庫里面獲取數據的方法。quickQuery' 函數的類型和 run 函數非常相似,只不過 quickQuery' 函數返回的是一個由查詢結果組成的列表而不是被改動的行數量。quickQuery' 函數通常與 SELECT 語句一起使用,正如以下代碼所示: ~~~ ghci> conn <- connectSqlite3 "test1.db" ghci> quickQuery' conn "SELECT * from test where id < 2" [] [[SqlString "0",SqlNull],[SqlString "0",SqlString "zero"],[SqlString "1",SqlString "one"]] ghci> disconnect conn ~~~ 正如之前展示過的一樣,quickQuery' 函數能夠接受可替換參數。上面的代碼沒有使用任何可替換參數,所以在調用 quickQuery' 的時候,我們沒有在函數調用的末尾給定任何的可替換值。quickQuery' 返回一個由行組成的列表,其中每個行都會被表示為 [SqlValue] ,而行里面的值會根據數據庫返回時的順序進行排列。在有需要的時候,用戶可以使用 fromSql 可以將這些值轉換為普通的 Haskell 類型。 因為 quickQuery' 的輸出有一些難讀,我們可以對上面的示例進行一些擴展,將它的結果格式化得更美觀一些。以下代碼展示了對結果進行格式化的具體方法: ~~~ -- file: ch21/query.hs import Database.HDBC.Sqlite3 (connectSqlite3) import Database.HDBC {- | 定義一個函數,它接受一個表示要獲取的最大 id 值作為參數。 函數會從 test 數據庫里面獲取所有匹配的行,并以一種美觀的方式將它們打印到屏幕上面。 -} query :: Int -> IO () query maxId = do -- 連接數據庫 conn <- connectSqlite3 "test1.db" -- 執行查詢并將結果儲存在 r 里面 r <- quickQuery' conn "SELECT id, desc from test where id <= ? ORDER BY id, desc" [toSql maxId] -- 將每個行轉換為 String let stringRows = map convRow r -- 打印行 mapM_ putStrLn stringRows -- 斷開與服務器之間的連接 disconnect conn where convRow :: [SqlValue] -> String convRow [sqlId, sqlDesc] = show intid ++ ": " ++ desc where intid = (fromSql sqlId)::Integer desc = case fromSql sqlDesc of Just x -> x Nothing -> "NULL" convRow x = fail $ "Unexpected result: " ++ show x ~~~ 這個程序所做的工作和本書之前展示過的 **ghci** 示例差不多,唯一的區別就是新添加了一個 convRow 函數。這個函數接受來自數據庫行的數據,并將它轉換為一個易于打印的 String 值。 注意,這個程序會直接通過 fromSql 取出 intid 值,但是在處理 fromSqlsqlDesc 的時候卻使用了 MaybeString 。不知道你是否還記得,我們在定義表的時候,曾經將表的第一列設置為不準包含 NULL 值,但是第二列卻沒有進行這樣的設置。所以,程序不需要擔心第一列是否會包含 NULL 值,只要對第二行進行處理就可以了。雖然我們也可以使用 fromSql 去將第二行的值直接轉換為 String ,但是這樣一來的話,程序只要遇到 NULL 值就會出現異常。因此,我們需要把 SQL 的 NULL 轉換為字符串 "NULL" 。雖然這個值在打印的時候可能會與字符串 'NULL' 出現混淆,但對于這個例子來說,這樣的問題還是可以接受的。讓我們嘗試在 **ghci** 里面調用這個函數: ~~~ ghci> :load query.hs [1 of 1] Compiling Main ( query.hs, interpreted ) Ok, modules loaded: Main. ghci> query 2 0: NULL 0: zero 1: one 2: two ~~~ ## 使用語句進行數據讀取操作 正如前面的《預備語句》一節所說,用戶可以使用預備語句進行讀取操作,并且在一些環境下,使用不同的方法從這些語句里面讀取出數據將是一件非常有用的事情。像 run 、 quickQuery' 這樣的常用函數實際上都是使用語句去完成任務的。 為了創建一個執行讀取操作的預備語句,用戶只需要像之前執行寫入操作那樣使用 prepare 函數來創建預備語句,然后使用 execute 去執行那個預備語句就可以了。在語句被執行之后,用戶就可以使用各種不同的函數去讀取語句中的數據。fetchAllRows' 函數和 quickQuery' 函數一樣,都返回 [[SqlValue]] 類型的值。除此之外,還有一個名為 sFetchAllRows' 的函數,它在返回每個列的數據之前,會先將它們轉換為 MaybeString 。最后,fetchAllRowsAL' 函數對于每個列返回一個 (String,SqlValue) 二元組,其中 String 類型的值是數據庫返回的列名。本章接下來的《數據庫元數據》一節還會介紹其他獲取列名的方法。 通過 fetchRow 函數,用戶可以每次只讀取一個行上面的數據,這個函數會返回 IO(Maybe[SqlValue]) 類型的值:當所有行都已經被讀取了之后,函數返回 Nothing ;如果還有尚未讀取的行,那么函數返回一個行。 ## 惰性讀取 前面的《惰性I/O》一節曾經介紹過如何對文件進行惰性 I/O 操作,同樣的方法也可以用于讀取數據庫中的數據,并且在處理可能會返回大量數據的查詢時,這種特性將是非常有用的。通過惰性地讀取數據,用戶可以繼續使用 fetchAllRows 這樣的方便的函數,不必再在行數據到達時手動地讀取數據。通過以謹慎的方式使用數據,用戶可以避免將所有結構都緩存到內存里面。 不過要注意的是,針對數據庫的惰性讀取比針對文件的惰性讀取要負責得多。用戶在以惰性的方式讀取完整個文件之后,文件就會被關閉,不會留下什么麻煩的事情。另一方面,當用戶以惰性的方式從數據庫讀取完數據之后,數據庫的連接仍然處于打開狀態,以便用戶繼續執行其他操作。有些數據庫甚至支持同時發送多個查詢,所以 HDBC 是無法在用戶完成一次惰性讀取之后就關閉連接的。 在使用惰性讀取的時候,有一點是非常重要的:在嘗試關閉連接或者執行一個新的查詢之前,一定要先將整個數據集讀取完。我們推薦你使用嚴格(strict)函數又或者以一行接一行的方式進行處理,從而盡量避免惰性讀取帶來的復雜的交互行為。 Tip 如果你是剛開始使用 HDBC ,又或者對惰性讀取的概念并不熟悉,但是又需要讀取大量數據,那么可以考慮通過反復調用 fetchRow 來獲取數據。這是因為惰性讀取雖然是一種非常強大而且有用的工具,但是正確地使用它并不是那么容易的。 要對數據庫進行惰性讀取,只需要使用不帶單引號版本的數據庫函數就可以了。比如 fetchAllRows 就是 fetchAllRows' 的惰性讀取版本。惰性函數的類型和對應的嚴格版本函數的類型一樣。以下代碼展示了一個惰性讀取示例: ~~~ ghci> conn <- connectSqlite3 "test1.db" ghci> stmt <- prepare conn "SELECT * from test where id < 2" ghci> execute stmt [] 0 ghci> results <- fetchAllRowsAL stmt [[("id",SqlString "0"),("desc",SqlNull)],[("id",SqlString "0"),("desc",SqlString "zero")],[("id",SqlString "1"),("desc",SqlString "one")]] ghci> mapM_ print results [("id",SqlString "0"),("desc",SqlNull)] [("id",SqlString "0"),("desc",SqlString "zero")] [("id",SqlString "1"),("desc",SqlString "one")] ghci> disconnect conn ~~~ 雖然使用 fetchAllRowsAL' 函數也可以達到取出所有行的效果,但是如果需要讀取的數據集非常大,那么 fetchAllRowsAL' 函數可能就會消耗非常多的內容。通過以惰性的方式讀取數據,我們同樣可以讀取非常大的數據集,但是只需要使用常數數量的內存。惰性版本的數據庫讀取函數會把結果放到一個塊里面進行求值;而嚴格版的數據庫讀取函數則會直接獲取所有結果,把它們儲存到內存里面,接著打印。 ## 數據庫元數據 在一些情況下,能夠知道一些關于數據庫自身的信息是非常有用的。比如說,一個程序可能會想要看看數據庫里面目前已有的表,然后自動創建缺失的表或者對數據庫的模式(schema)進行更新。而在另外一些情況下,程序可能會需要根據正在使用的數據庫后端對自己的行為進行修改。 通過使用 getTables 函數,我們可以取得數據庫目前已定義的所有列表;而 describeTable 函數則可以告訴我們給定表的各個列的定義信息。 調用 dbServerVer 和 proxiedClientName 可以幫助我們了解正在運行的數據庫服務器,而 dbTransactionSupport 函數則可以讓我們了解到數據庫是否支持事務。以下代碼展示了這三個函數的調用示例: ~~~ ghci> conn <- connectSqlite3 "test1.db" ghci> getTables conn ["test"] ghci> proxiedClientName conn "sqlite3" ghci> dbServerVer conn "3.5.9" ghci> dbTransactionSupport conn True ghci> disconnect conn ~~~ describeResult 函數返回一組 [(String,SqlColDesc)] 類型的二元組,二元組的第一個項是列的名字,第二個項則是與列相關的信息:列的類型、大小以及這個列能夠為 NULL 等等。完整的描述可以參考 HDBC 的 API 手冊。 需要注意一點是,某些數據庫并不能提供所有這些元數據。在這種情況下,程序將引發一個異常。比如 Sqlite3 就不支持前面提到的 describeResult 和 describeTable 。 ## 錯誤處理 HDBC 在錯誤出現時會引發異常,異常的類型為 SqlError 。這些異常會傳遞來自底層 SQL 引擎的信息,比如數據庫的狀態、錯誤信息、數據庫的數字錯誤代號等等。 因為 **ghci** 并不清楚應該如何向用戶展示一個 SqlError ,所以這個異常將導致程序停止,并打印一條沒有什么用的信息。就像這樣: ~~~ ghci> conn <- connectSqlite3 "test1.db" ghci> quickQuery' conn "SELECT * from test2" [] *** Exception: (unknown) ghci> disconnect conn ~~~ 上面的這段代碼因為使用了 SELECT 去獲取一個不存在的表,所以引發了錯誤,但 **ghci** 返回的的錯誤信息并沒有說清楚這一點。通過使用 handleSqlError 輔助函數,我們可以捕捉 SqlError 并將它重新拋出為 IOError 。這種格式的錯誤可以被 **ghci** 打印,但是這種格式會使得用戶比較難于通過編程的方式來獲取錯誤信息的指定部分。以下是一個使用 handleSqlError 處理異常的例子: ~~~ ghci> conn <- connectSqlite3 "test1.db" ghci> handleSqlError $ quickQuery' conn "SELECT * from test2" [] *** Exception: user error (SQL error: SqlError {seState = "", seNativeError = 1, seErrorMsg = "prepare 20: SELECT * from test2: no such table: test2"}) ghci> disconnect conn ~~~ 這個新的錯誤提示具有更多信息,它甚至包含了一條說明 test2 表并不存在的消息,這比之前的錯誤提示有用得多了。作為一種標準實踐(standard practice),很多 HDBC 程序員都將 main=handleSqlError$do 放到程序的開頭,確保所有未被捕獲的 SqlError 都會以更有效的方式被打印。 除了 handleSqlError 之外,HDBC 還提供了 catchSql 和 handleSql 這兩個函數,它們類似于標準的 catch 函數和 handle 函數,主要的區別在于 catchSql 和 handleSql 只會中斷 HDBC 錯誤。想要了解更多關于錯誤處理的信息,可以參考本書第 19 章《錯誤處理》一章。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看