數據庫API規範 v2.0 (PEP 249)

數據庫API規範 v2.0 (PEP 249)

GitHub@orca-j35,全部筆記均託管於 python_notes 倉庫
歡迎任何形式的轉載,但請務必註明出處。html

參考:python

介紹

PEP 249 是 Python 數據庫 API 規範的 2.0 版本,PEP 248 是 1.0 版本。PEP 249 不只包含 Python 數據庫 API 規範,還描述了一組具有通用性的可選擴展。早先的 1.0 版本(PEP 248)依然具有參考價值,但鼓勵模塊編寫者在開發新接口時以 PEP 249 爲參考。PEP 249 的目標是讓"數據庫訪問模塊"提供類似的接口,實現一致性,從而讓代碼便於移植。mysql

若是須要了解與數據庫接口相關的各類庫,請看 Database Topic Guidegit

模塊接口

構造器

本規範約定,需經過 Connection 對象來訪問數據庫,所以"數據庫訪問模塊"必須提供用來建立 Connection 對象的構造器。程序員

connect🪓

🪓connect( parameters... )github

該構造器用於建立數據庫鏈接,返回值是 Connection 對象。算法

parameters... 表示與數據庫相關的參數。sql

註釋:數據庫

構造器 connect( parameters... ) 的參數應以關鍵字參數的形式實現,並按照下表中的前後順序排列:後端

Parameter Meaning
dsn Data source name as string
user User name as string (optional)
password Password as string (optional)
host Hostname (optional)
database Database name (optional)

E.g. a connect could look like this:

connect(dsn='myhost:MYDB', user='guido', password='234$')

模塊級全局變量

數據庫訪問模塊必須定義本節中給出的各類全局變量

apilevel🔧

🔧apilevel

一個字符串常量,表示模塊支持的 DB API 版本。

目前只容許使用字符串 "1.0" (對應 PEP 248) 和 "2.0" (對應 PEP 249)。若是未給出該常量,則假設模塊使用 DB-API 1.0。

threadsafety🔧

🔧threadsafety

一個整數常量,用於說明模塊接口支持的線程安全級別,可選值以下:

threadsafety Meaning
0 Threads may not share the module.
1 Threads may share the module, but not connections.
2 Threads may share the module and connections.
3 Threads may share the module, connections and cursors.

share 意味着兩個線程在使用同一個資源時,無需使用互斥信號(mutex semaphore)來實現資源鎖。注意,即便已使用互鎖機制(mutex)來管理訪問權限,也沒法始終確保外部資源的線程安全: 由於外部資源可能會依賴於全局變量,或依賴於你沒法控制的其它外部資源。

paramstyle🔧

一個字符串常量,用於說明模塊接口指望的參數標記(parameter marker)的格式化風格,可選值以下:

paramstyle Meaning
qmark Question mark style, e.g. ...WHERE name=?
numeric Numeric, positional style, e.g. ...WHERE name=:1
named Named style, e.g. ...WHERE name=:name
format ANSI C printf format codes, e.g. ...WHERE name=%s
pyformat Python extended format codes, e.g. ...WHERE name=%(name)s

模塊實現者應優先考慮使用 numeric, namedpyformat,由於它們清晰度和靈活性更佳。

import sqlite3
print(sqlite3.paramstyle) #> qmark

import pymysql
print(pymysql.paramstyle) #> pyformat

import mysql.connector
print(mysql.connector.paramstyle) #> pyformat

可見,sqlite3 使用 ? 做爲參數標記,所以可以使用如下語句插入數據:

cursor.execute("INSERT INTO user (id,name) VALUES (?, ?)", (6, 'liuyi'))

Exceptions...

數據庫訪問模塊應經過下述 Exception 類(或其子類)來提供所有錯誤信息:

  • Warning

    必須繼承自 Python StandardError,遇到下述相似狀況時,應拋出該異常:

    • 在插入時遇到數據被截斷
  • Error

    必須繼承自 Python StandardError,模塊中其它全部 error 異常都應是 Error 的子類。你能夠在單個 except 語句中使用 Error 來捕捉全部 error 異常。Warning 不該被視爲 error,所以不是 Error 的子類。

  • InterfaceError

    必須是 Error 的子類表示那些與數據庫接口有關,而與數據庫自己無關的錯誤。

  • DatabaseError

    必須是 Error 的子類,表示那些與數據庫有關的錯誤。

  • DataError

    必須是 DatabaseError 的子類,表示因處理數據而致使的錯誤,例如: 除數是 0;或數值超出範圍

  • OperationalError

    必須是 DatabaseError 的子類,表示與數據庫操做相關的錯誤,而且不必定是在程序員的控制之下形成的錯誤。好比,意外狀況致使斷開鏈接;找不到數據源名稱;沒法處理事務,處理期間發生內存分配錯誤等

  • IntergrityError

    必須是 DatabaseError 的子類,表示數據操做違反了數據庫的完整性約束。好比,外鍵檢查失敗。

  • InternalError

    必須是 DatabaseError 的子類,遇到數據庫內部錯誤時會拋出該異常。好比,遊標(cursor)再也不有效,或事務不一樣步等。

  • ProgrammingError

    必須是 DatabaseError 的子類,表示程序邏輯出錯。好比,未找到表或表已經存在;SQL 語句中的語法錯誤;指定了錯誤的參數數量等。

  • NotSupportedError

    必須是 DatabaseError 的子類,當使用了數據庫不支持的 API 或方法時會拋出該異常。好比,在不支持 SQL 事務(或已關閉事務)的 connection 上請求 .rollback() 方法。

繼承結構

下面是 Exception 類的繼承結構:

StandardError
|__Warning
|__Error
   |__InterfaceError
   |__DatabaseError
      |__DataError
      |__OperationalError
      |__IntegrityError
      |__InternalError
      |__ProgrammingError
      |__NotSupportedError
⚠The values of these exceptions are not defined. They should give the user a fairly good idea of what went wrong, though.

Connection 對象

Connection 對象必須實現本節中給出的各類方法。

Connection 方法

.close()🔨

當即關閉鏈接(而不是每次都調用 .__del__() 方法)

在 Connection 對象上調用 .close() 後,"鏈接"將再也不可用。若是試圖對此"鏈接"作任何操做都會拋出 Error (或其子類)異常。這一樣適用於試圖使用此"鏈接"的全部遊標(cursor)對象。

⚠若是沒有 commit 變動就關閉了"鏈接",將隱式執行 rollback 操做。

.commit()🔨

將任何掛起的事務(transaction)提交到數據庫。

⚠若是數據庫支持自動提交(auto-commit)功能,必須先關閉自動提交功能。還能夠提供一個接口方法來從新開啓自動提交功能。

若是數據庫模塊不支持事務,則應使用一個空方法(void functionality)來實現 .commit()

.rollback()🔨

這是一個可選方法,由於並不是全部數據庫都支持事務。

若是數據庫確實支持事務,.rollback() 方法會使數據庫回滾到任何掛起事務的以前。若是沒有 commit 變動就關閉了"鏈接",將隱式執行 rollback 操做。

註釋:

If the database does not support the functionality required by the method, the interface should throw an exception in case the method is used.

The preferred approach is to not implement the method and thus have Python generate an AttributeError in case the method is requested. This allows the programmer to check for database capabilities using the standard hasattr() function.

For some dynamically configured interfaces it may not be appropriate to require dynamically making the method available. These interfaces should then raise aNotSupportedError to indicate the non-ability to perform the roll back when the method is invoked.

.cursor()🔨

使用 Connection 對象返回一個新的 Cursor 對象。

若是數據庫並無直接提供遊標的概念,則模塊必須使用其餘方式來模擬規範所需的「遊標」。

數據庫接口能夠選擇是否爲 .cursor() 提供一個字符串參數來構建具名 cursor。該功能並不屬於規範中的一部分,由於它會使 .fetch*() 方法的語義複雜化。

Cursor 對象

Cursor 對象表示數據庫的遊標(cursor),用於管理 fetch 操做的上下文(context)。由同一個 connection 建立的 cursor 之間並非孤立的(isolated),某個 cursor 對數據庫進行的任何改動都會被其它 cursor 當即看到。由不一樣 connection 建立的 cursor 之間多是相互孤立的,也可能不是相互孤立的,這取決於事務支持的實現方式。(還能夠看看 connection 的 rollback().commit() 方法)

Cursor 對象必須實現本節中給出的各類方法和屬性。

Cursor 屬性

.description🔧

該只讀屬性自己是一個序列,序列中的每一個元素又對應爲一個子序列,子序列由如下 7 個條目組成。子序列中存放着對結果列(column)的描述信息,每一個子序列對應一個結果列。

  • name
  • type_code - 此列在數據庫中的類型
  • display_size
  • internal_size
  • precision
  • scale
  • null_ok

前兩個條目(nametype_coe)是必填項,後面五個是選填項。若是不能提供有意義的值,則設置爲 None

在遇到如下兩種狀況時,.description 的值是 None:

  • curcor 還沒有經過 .execute*() 方法執行操做
  • 被執行的操做不會返回任何行(row)

若是須要了解 type_code 的含義,請參考 Type Objects 小節

.rowcount🔧

該只讀屬性用於存放如下兩種狀況涉及到的行數:

  • 最近一次 .execute*() 調用所得結果的行數( 針對 SELECT 等 DQL 語句)
  • 最近一次 .execute*() 調用所影響的行數( 針對 UPDATE 或 INSERT 等 DML 語句)
註釋:

The term number of affected rows generally refers to the number of rows deleted, updated or inserted by the last statement run on the database cursor. Most databases will return the total number of rows that were found by the corresponding WHERE clause of the statement. Some databases use a different interpretation for UPDATEs and only return the number of rows that were changed by the UPDATE, even though the WHERE clause of the statement may have found more matching rows. Database module authors should try to implement the more common interpretation of returning the total number of rows found by the WHERE clause, or clearly document a different interpretation of the .rowcount attribute.

在遇到如下兩種狀況時,.rowcount 的值爲 -1:

  • cursor 還沒有執行過 .execute*() 方法
  • 接口沒法肯定最後一次操做的涉及到的行數

    在從此的 DB API 規範中會從新定義此種狀況,將返回值由 -1 改成 None

The rowcount attribute may be coded in a way that updates its value dynamically. This can be useful for databases that return usable rowcount values only after the first call to a .fetch*() method.
.arraysize🔧

該可讀/寫屬性表示每次調用 fetchmany() 時可獲取到的結果集中的行數。該字段的默認值是 1,意味着每次調用 fetchmany() 函數會獲取結果集中的一行數據。

在實現 .fetchmany() 時必須遵照 .arraysize 字段,但在實現過程當中也能夠一次一行的和數據庫交互。在實現 executemany() 方法時,也可以使用 .arrarsize 字段。

Cursor 方法

.callproc()🔨

🔨.callproc( procname [, parameters ] )

(此方法是可選實現,由於並不是全部數據庫都支持"存儲過程(stored procedures)")

註釋:

If the database does not support the functionality required by the method, the interface should throw an exception in case the method is used.

The preferred approach is to not implement the method and thus have Python generate an AttributeError in case the method is requested. This allows the programmer to check for database capabilities using the standard hasattr() function.

For some dynamically configured interfaces it may not be appropriate to require dynamically making the method available. These interfaces should then raise aNotSupportedError to indicate the non-ability to perform the roll back when the method is invoked.

❓Q: 什麼是"存儲過程"?
A: 可參考《SQL 必知必會》->第19課 使用存儲過程,簡單來講存儲過程就好像是自定義函數。

.callproc() 的功能是調用名爲 procname 的"存儲過程",參數序列 parameters 中包含的條目即是"存儲過程"所需的參數。調用結果將被表示爲輸入序列的副本,並在返回前對參數進行修改:

  • "存儲過程"的輸入(IN)參數保持不變
  • 輸出(OUT)參數和輸入/輸出(INOUT)參數將被替換爲可能的新值

"存儲過程"也能夠提供一個做爲輸出的結果集。此時必須經過標準的 .fetch*() 方法來獲取。

📝某些庫的實現方式可能並不兼容本條約定,好比 pymysql:

"""Defining the Stored Routine in MySQL:
CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT)
BEGIN
SET pProd := pFac1 * pFac2;
END
"""
import pymysql
try:
    conn = pymysql.connect(
        host="localhost",
        port=3306,
        user='root',
        password='orca_j35',
        database='test',
    )
    cursor = conn.cursor()
    args = (5, 5, 0)
    # callproc()直接返回輸入序列,不兼容本條約定
    print(cursor.callproc('multiply', args))  #> (5, 5, 0)
    print(cursor.execute('SELECT @_multiply_2'))  #> 1
    print(cursor.fetchall()) #> ((25,),)
except Exception as ex:
    print(ex)
.close()🔨

🔨.close()

當即關閉 cursor (而不是每次都調用 .__del__() 方法)

在 cursor 對象上調用 .close() 後,cursor 將不在可用。若是以後試圖使用這個 cursor 執行任何操做,都會引起一個 Error 或其(子類)的異常。

.execute()🔨

🔨.execute(operation [, parameters])

準備並執行一個數據庫操做: query 或 command

parameters 能夠是序列或映射,parameters 中的元素將被綁定到操做 operation 內含的變量中。需以數據庫特定的標識符來表示變量(詳見 paramstyle 屬性)

註釋:

The module will use the __getitem__ method of the parameters object to map either positions (integers) or names (strings) to parameter values. This allows for both sequences and mappings to be used as input.

The term bound refers to the process of binding an input value to a database execution buffer. In practical terms, this means that the input value is directly used as a value in the operation. The client should not be required to "escape" the value so that it can be used — the value should be equal to the actual database value.

cursor 將保留(retain)對 operation 的引用。若是再次傳入相同的 operation 對象,那麼 cursor 能夠優化這種行爲。這對於那種(屢次)使用相同的 operation,但會將不一樣 parameters 綁定到該 operation 的狀況最有效。

爲了在重用 operation 時得到最大的效率,最好使用 .setinputsizes() 方法提早指定 parameters 中各參數的類型和大小。參數與預約義信息不匹配也屬於合法行爲,但在實現上應考慮對可能會出現的效率損失進行補償(compensate)。

parameters 也能夠是元組列表(list of tuples),例如,在單個操做中插入多行。可是不推薦這種作法: 應使用 .executemany() 方法。

此方法未定義返回值。

示例 -

import pymysql
print(pymysql.paramstyle) #> pyformat
conn = pymysql.connect(
        host="localhost",
        port=3306,
        user='root',
        password='orca_j35',
        database='test',
    )
cursor = conn.cursor()
cursor.execute("INSERT INTO user(name) VALUES (%s)", ('小劉', ))
cursor.execute('SELECT name,id FROM user Where name=%s', ('小劉', ))
print(cursor.fetchall())
#> (('小劉', 15),)
.executemany()🔨

🔨.executemany( operation, seq_of_parameters )

準備一個數據庫操做(query 或 command),而後逐一使用 seq_of_parameters 中包含的序列或映射來執行該數據庫操做。也就是說,.executemany() 方法能夠在一次調用中處理 seq_of_parameters 中的全部序列。

數據庫模塊有兩種實現 .executemany() 的方式:

  • 第一種是屢次調用 .execute() 方法
  • 第二種是使用數組操做(array operations)

若是 .executemany() 執行了會生成一個或多個結果集(result sets)的 operation,便會構成未定義行爲;當檢測到經過調用操做建立告終果集時,容許(非必須)實現拋出一個異常。

適用於 .execute() 的註釋也適用於此方法。

此方法未定義返回值。

.fetchone()🔨

🔨.fetchone()

獲取"查詢結果集(query result set)"中的下一行,並將其表示爲單個序列。若是沒有更多可用數據則會返回 None

註釋:

數據庫接口可能會使用數組和其餘優化方式來實現數據行的獲取。所以在調用 fetchone() 時,並不能保證相應的 cursor 只會向前移動一行,可能會移動多行。

若是先前調用的 .execute*() 方法並未產生任何結果集,或者還沒有調用 .execute*(),則應拋出 Error(或其子類)異常。

.fetchmany()🔨

🔨.fetchmany([size=cursor.arraysize])

向後獲取查詢結果集中的多個行,並返回一個包含各行數據的序列(e.g. a list of tuples),每一行數據會被表示爲一個子序列。當結果集中沒有更多可用行時,則會返回一個空序列。

每次調用所獲取的行數由 size 參數決定。若是沒有傳入 size 參數,則由 cursor 的 arraysize 屬性來肯定要獲取的行數。該方法應盡力嘗試獲取 size 行數據,若是結果集中的可用行數小於 size,則返回的函數可能少於 size

若是先前調用的 .execute*() 方法並未產生任何結果集,或者還沒有調用 .execute*(),則應拋出 Error(或其子類)異常。

⚠: size 參數的大小會影響性能。爲了得到最佳性能,一般最好將 cursor 的 arraysize 屬性用做 size 的值。若是使用自定義 size 值,那麼最好在每次調用 .fetchmany() 時都使用相同的 size 值。

.fetchall()🔨

🔨.fetchall()

獲取查詢結果集中全部(剩餘)的行,並返回一個包含各行數據的序列(e.g. a list of tuples),每一行數據會被表示爲一個子序列。

⚠cursor 的 arraysize 屬性可能會影響此操做的性能。

若是先前調用的 .execute*() 方法並未產生任何結果集,或者還沒有調用 .execute*(),則應拋出 Error(或其子類)異常。

.nextset()🔨

🔨.nextset()

(此方法是可選實現,由於並不是全部數據庫都支持"多結果集(multiple result sets)")

註釋:

If the database does not support the functionality required by the method, the interface should throw an exception in case the method is used.

The preferred approach is to not implement the method and thus have Python generate an AttributeError in case the method is requested. This allows the programmer to check for database capabilities using the standard hasattr() function.

For some dynamically configured interfaces it may not be appropriate to require dynamically making the method available. These interfaces should then raise aNotSupportedError to indicate the non-ability to perform the roll back when the method is invoked.

此方法會使 cursor 跳至下一個可用的結果集,並丟棄當前結果集中剩餘的全部行。

若是不存在更多的結果集,該方法將返回 None;不然,該方法會返回一個真值,在這以後調用的 .fetch() 方法將返回下一個結果集中的行。

若是先前調用的 .execute*() 方法並未產生任何結果集,或者還沒有調用 .execute*(),則應拋出 Error(或其子類)異常。

"""在MySQL中定義一個返回多結果集的存儲過程:
CREATE DEFINER=`root`@`localhost` PROCEDURE `multi_select`()
BEGIN
    SELECT name FROM `user`;
    SELECT id FROM `user`;
END
"""
"""user表中的數據以下:
 name |  id
"小明" | "1"
"小紅" | "2"
"小剛" | "3"
"小燦" | "4"
"""
import pymysql
conn = pymysql.connect(
        host="localhost",
        port=3306,
        user='root',
        password='orca_j35',
        database='test',
    )
cursor = conn.cursor()
cursor.callproc('multi_select')
print(cursor.fetchone())
if cursor.nextset():
    print(cursor.fetchall())

輸出:

('小明',)
((1,), (2,), (3,), (4,))
.setinputsizes()🔨

🔨.setinputsizes(sizes)

.execute( operation [, parameters])

.executemany( operation, seq_of_parameters )

在調用 .execute*() 以前,可以使用該方法預約義 .execute*() 的"操做參數"(parametersseq_of_parameters)的內存區域。

sizes 參數應是一個序列,該序列中的項應和"輸入參數"中的項相互對應,而且是相同的 Type Object 或者是表示字符串參數的最大長度的整數。若是某一項的值是 None,則不會爲該列保留預約義的內存區域——這樣能夠避免爲大尺寸的"輸入參數"預留過大的內存區域。

應在使用 .execte*() 以前調用 .setinputsizes()

數據庫模塊的實現者能夠自由選擇是否實現此方法,而且模塊的使用者也能夠自由選擇是否使用此方法。

.setoutputsize()🔨

🔨.setoutputsize(size [, column])

爲獲取"大數據列"(e.g. LONGs, BLOBs, etc.)而設置的列緩衝區。column 參數用於指定目標列在結果序列中的索引位置。若是不設置 column,則默認爲 cursor 中的所有大數據列設置尺寸爲 size 的緩衝區。

應在使用 .execte*() 以前調用 .setoutputsize()

數據庫模塊的實現者能夠自由選擇是否實現此方法,而且模塊的使用者也能夠自由選擇是否使用此方法。

數據類型的構造器...

許多數據庫都須要具有特定格式的輸入,以便經過 SQL 操做將輸入參數綁定到數據庫中。例如,假如要向數據庫的 DATE 列輸入數據,就必須以"特定的字符串格式"將輸入數據綁定到數據庫中。"Row ID" 列或 large binary 項(e.g. blobs or RAW columns)也存在相似的問題。這點對於 Python 來講並不友好,由於傳遞給 execute*() 的參數是無類型的。當數據庫模塊看到 Python 字符串對象時,它並不知道應該將其綁定到何種數據庫類型 —— 數據庫模塊會很困惑,是該綁定到 CHAR 列,仍是綁定到 BINARY 項,抑或是綁定到 DATE 喃?

爲了解決上述問題,模塊必須提供如下構造器,從而能夠建立可以保持特定值的對象。當咱們將下述對象傳遞給 cursor 的方法時,模塊就可以檢查到輸入參數的正確類型,並將其綁定到數據庫中。

模塊將包含如下的構造函數:

  • Date(year, month, day)

    此函數會構造一個保存 data 值的對象。

  • Time(hour, minute, second)

    此函數會構造一個保存 time 值的對象。

  • Timestamp(year, month, day, hour, minute, second)

    此函數會構造一個保存 time stamp 值的對象。

  • DateFromTicks(ticks)

    此函數會根據給定的 ticks 值來構造一個保存 data 值的對象。ticks 是自紀元(epoch)以來秒數,詳見 the standard Python time module

  • TimeFromTicks(ticks)

    此函數會根據給定的 ticks 值來構造一個保存 time 值的對象。ticks 是自紀元(epoch)以來秒數,詳見 the standard Python time module

  • TimestampFromTicks(ticks)

    此函數會根據給定的 ticks 值來構造一個保存 time stamp 值的對象。ticks 是自紀元(epoch)以來秒數,詳見 the standard Python time module

  • Binary(string)

    此函數會構造一個可以保存 binary (long) 字符串值的對象。

    # Binary's source code in pymysql
    def Binary(x):
        """Return x as a binary type."""
        if PY2:
            return bytearray(x)
        else:
            return bytes(x)

    詳見筆記: ﹝bytes.md﹞

在輸入/輸出中,SQL 的 NULL 值會表示爲 Python 的 None 單例對象。

⚠: 在數據庫接口中使用 Unix ticks 可能會致使沒必要要的麻煩,由於 Unix ticks 僅覆蓋有限的日期範圍

類型對象...

Cursor 對象的 .descripton 字段會返回"查詢結果"中每一列的相關信息,其中的 type_code 必須等於下述某個 Type Object,同一個 Type Object 可能會與多個類型代碼(type code)相等 (e.g. DATETIME could be equal to the type codes for date, time and timestamp columns; see the Implementation Hints below for details).

模塊將包含如下單例(singleton)形式的 Type Object:

  • STRING type

    此 Type Object 用於描述數據庫中的 string-based 列,如 CHAR

  • BINARY type

    此 Type Object 用於描述數據庫中的 (long) binary 列,如 LONG, RAW, BLOB

  • NUMBER type

    此 Type Object 用於描述數據庫中的 numeric 列

  • DATETIME type

    此 Type Object 用於描述數據庫中的 date/time 列

  • ROWID type

    此 Type Object 用於描述數據庫中的 "Row ID" 列

    擴展閱讀: Equivalent of Oracle’s RowID in MySQL

給模塊實現者的建議

譯者注: 這部份內容是給模塊的實現者的一些建議。若是你是模塊的使用者,無需過度關心這部份內容,隨意瀏覽一下就好,原文內容也比較簡單,所以我沒有進行翻譯。若是你想了解模塊的具體實現過程,那麼建議你仔細看一看。
  • Date/time objects can be implemented as Python datetime module objects (available since Python 2.3, with a C API since 2.4) or using the mxDateTime package (available for all Python versions since 1.5.2). They both provide all necessary constructors and methods at Python and C level.
  • Here is a sample implementation of the Unix ticks based constructors for date/time delegating work to the generic constructors:

    import time
    
    def DateFromTicks(ticks):
        return Date(*time.localtime(ticks)[:3])
    
    def TimeFromTicks(ticks):
        return Time(*time.localtime(ticks)[3:6])
    
    def TimestampFromTicks(ticks):
        return Timestamp(*time.localtime(ticks)[:6])
  • The preferred object type for Binary objects are the buffer types available in standard Python starting with version 1.5.2. Please see the Python documentation for details. For information about the C interface have a look at Include/bufferobject.h and Objects/bufferobject.c in the Python source distribution.
  • This Python class allows implementing the above type objects even though the description type code field yields multiple values for on type object:

    class DBAPITypeObject:
        def __init__(self,*values):
            self.values = values
        def __cmp__(self,other):
            if other in self.values:
                return 0
            if other < self.values:
                return 1
            else:
                return -1

    The resulting type object compares equal to all values passed to the constructor.

  • Here is a snippet of Python code that implements the exception hierarchy defined above:

    import exceptions
    
    class Error(exceptions.StandardError):
        pass
    
    class Warning(exceptions.StandardError):
        pass
    
    class InterfaceError(Error):
        pass
    
    class DatabaseError(Error):
        pass
    
    class InternalError(DatabaseError):
        pass
    
    class OperationalError(DatabaseError):
        pass
    
    class ProgrammingError(DatabaseError):
        pass
    
    class IntegrityError(DatabaseError):
        pass
    
    class DataError(DatabaseError):
        pass
    
    class NotSupportedError(DatabaseError):
        pass

    In C you can use the PyErr_NewException(fullname, base, NULL) API to create the exception objects.

可選DB API擴展...

在 DB API 2.0 的生命週期中,模塊編寫者常常會編寫超出 DB API 2.0 規範的實現。爲了加強兼容性並(爲本規範的將來版本)提供乾淨的升級路徑,本節爲核心 DB API 2.0 規範定義了一組通用擴展。

與全部 DB API 可選功能同樣,數據庫模塊的編寫者能夠自由決定是否須要實現這些額外的字段和方法。若是調用了未實現的屬性,則會拋出 AttributeError;對於只能在運行時才能檢查可用性的屬性,則會拋出 NotSupportedError

爲了讓模塊使用者哪些擴展 API 可用,建議模塊編寫者經過 Python warning 框架向程序員發出 Python warning。爲了使 warning 功能可用,必須對警告消息進行標準化,以便可以屏蔽它們。「標準的消息」是指下述內容中提到的 Warning Message

  • 🧩Cursor.rownumber

    只讀屬性,表示 cursor 在當前結果集中的索引位置(起點是 0)。若是沒法肯定索引位置,則會返回 None

    索引位置是指 cursor 在序列(結果集)中的位置。下一次 fetch 操做將獲取序列(結果集)中第 .rownumber 行。

    Warning Message: "DB-API extension cursor.connection used"

  • 🧩Connection.Error, Connection.ProgrammingError, etc.

    全部按照 DB API 標準定義的異常類都應做爲 Connection 對象的屬性對外公開(在模塊做用域可用的除外)。

    這些屬性可簡化多鏈接(multi-connection)環境中的錯誤除了。

    Warning Message: "DB-API extension connection.<exception> used"

  • 🧩Cursor.connection

    只讀屬性,返回建立 Cursor 對象的 Connection 對象的引用。

    該屬性可簡化在多鏈接(multi-connection)環境中編寫多態(polymorph)代碼的過程。

    Warning Message: "DB-API extension cursor.connection used"

  • 🧩Cursor.scroll(value [, mode='relative' ])

    改變 cursor 在結果集中的索引位置。

    若是 mode='relative' (默認狀況),則將 value 視做結果集中當前位置的偏移量;若是 mode='absolute',則將 value 視做結果集中的絕對目標位置。

    Note

    This method should use native scrollable cursors, if available, or revert to an emulation for forward-only scrollable cursors. The method may raise NotSupportedError to signal that a specific operation is not supported by the database (e.g. backward scrolling).

    Warning Message: "DB-API extension cursor.scroll() used"

  • 🧩Cursor.messages

    該字段是一個 list 對象,從 cursor 的底層數據庫中接受到的全部消息(異常類、異常值)都會以元組形式追加到 .messages 列表中。

    在調用各類標準的 cursor 方法時(.fetch*() 除外)都會自動清理 .messages 列表,從而避免佔用過多的內存。還可用經過 del cursor.messages[:] 手動清理 .messages 列表。

    由數據庫生成的全部 error 和 warning 消息都會被放 .messages 列表中,所以用戶能夠經過檢查 .messages 來覈查 cursor 方法調用的 SQL 操做是否正確。

    使用 .messages 的目的是消除對 Warning 異常的需求(某些 warning 實際上只包含信息字符)

    Warning Message: "DB-API extension cursor.messages used"

  • 🧩Connection.messages

    Cursor.messages 相同,不過列表中的信息是面向 connection 的。

    在調用各類標準的 connection 方法時(執行調用以前)都會自動清理 .messages 列表,從而避免佔用過多的內存。還可用經過 del connection.messages[:] 手動清理 .messages 列表。

    Warning Message: "DB-API extension connection.messages used"

  • 🧩Cursor.next()

    .fetchone() 擁有相同的語義——從當前執行的 SQL 語句中返回下一行。在高於 Python 2.2 版本中,當結果集耗盡時會拋出 StopIteration 異常;在低於 Python 2.2 的版本中沒有 StopIteration 異常,可使用 IndexError 替代。實例:

    # source code in mysql.connector
        def next(self):
            """Used for iterating over the result set."""
            return self.__next__()
    
        def __next__(self):
            """
            Used for iterating over the result set. Calles self.fetchone()
            to get the next row.
            """
            try:
                row = self.fetchone()
            except errors.InterfaceError:
                raise StopIteration
            if not row:
                raise StopIteration
            return row

    Warning Message: "DB-API extension cursor.next() used"

  • 🧩Cursor.__iter__()

    讓 cursors 對象兼容迭代器協議。

    譯者注: 若是 Cursor 實現了 __next__(),直接返回 self 便可;若是沒有實現,則須要返回一個可迭代對象,例如:

    # source code in mysql.connector
    def __iter__(self):
        """
        Iteration over the result set which calls self.fetchone()
        and returns the next row.
        """
        return iter(self.fetchone, None)
    註釋:

    Implementation Note: Python C extensions will have to implement the tp_iter slot on the cursor object instead of the .__iter__() method.

    Warning Message: "DB-API extension cursor.__iter__() used"

  • 🧩Cursor.lastrowid

    只讀屬性,將提供最近一次修改的行的 rowid (大多數數據庫僅在執行單個 INSERT 操做時會返回 rowid)。若是 SQL 操做未設置 rowid,或者數據庫不支持 rowid,則會將該屬性設置爲 None

    若是最後一次執行的 SQL 語句修改了多個行,此時 .lastrowid 的語言沒法定義。例如,用 executemany() 執行 INSERT 語句。

    Warning Message: "DB-API extension cursor.lastrowid used"

可選錯誤處理擴展...

核心 DB API 規範僅引入了一組異常,在某些狀況下,異常可能對程序的流程形成極大的破壞,甚至會致使沒法執行。

對於這種狀況,爲了簡化處理數據庫時的錯誤處理(error handling),數據庫模塊的做者能夠選擇是否實現可以由用戶自定義的錯誤處理器(handler)。本節將介紹定義這些錯誤處理器的標準方法。

在建立 cursor 時,cursor 應從 connection 對象處繼承 .errorhandler 的設置。

  • 🧩Connection.errorhandler, Cursor.errorhandler

    以上兩個屬性均是可讀/寫屬性,它們被用來引用一個錯誤處理器,以便在知足錯誤條件時調用。

    錯誤處理程序必須是一個可調用的 Python 對象,且具有如下參數:

    errorhandler(connection, cursor, errorclass, errorvalue)

    參數說明:

    • connection - cursor 操做所屬的 connection 對象
    • cursor - cursor 對象,若是錯誤處理器不會應用於 cursor 對象,則實參值爲 None
    • errorclass - error 類,會使用 errorvalue 做爲構造參數進行實例化

標準錯誤處理器應將錯誤信息添加到相應的 .messages 屬性(Connection.messagesCursor.messages),並拋出由 errorclasserrorvalue 定的異常。

若是沒有設置 .errorhandler (即該字段值是 None),則會採用外層的標準錯誤處理方案。

Warning Message: "DB-API extension .errorhandler used"

可選二階段提交擴展

—— Optional Two-Phase Commit Extensions

許多數據庫都支持"二階段提交(two-phase commit - TPC)"。TPC 容許在管理事務時能夠跨多個數據庫鏈接和其它資源。

❓Q: 什麼是 TPC ?
A: 二階段提交(英語:Two-phase Commit)是指在計算機網絡以及數據庫領域內,爲了使基於分佈式系統架構下的全部節點在進行事務提交時保持一致性而設計的一種算法。另見:

若是數據庫後端支持 TPC,而且數據庫模塊的做者但願公開支持 TPC,則應實現如下 API。若是隻能在運行時才能檢查數據庫後端對 TPC 的支持,當遇到不支持的狀況時,應拋出 NotSupportedError

TPC Transaction IDs...

—— TPC 事務 ID

大多數數據庫都遵循 XA 規範,所以 transaction ID 應包含如下三個組件:

  • a format ID
  • a global transaction ID
  • a branch qualifier - 分支限定符

對於特定的全局事務(global transaction),前兩個組件對於全部資源應該都是相同的。應爲全局事務中的每一個資源分配不一樣的 branch qualifier。

上述組件必須知足如下標準:

  • format ID: 一個 32 位的非負整數
  • global transaction ID & branch qualifier: 不超過 64 個字符的 byte 字符串

應使用 .xid() Connection 方法建立 transaction ID:

  • 🧩.xid(format_id, global_transaction_id, branch_qualifier)

    返回一個適合傳遞給 connection 的 .tpc_*() 方法的自定義 transaction ID 對象。

    若是數據鏈接不支持 TPC,應拋出 NotSupportedError 異常。

    .xid() 返回的自定義 transaction ID 對象的類型,並無強制要求,但 transaction ID 對象必須具有"序列"行爲,並容許訪問其中的三個組件。一個合格的數據庫模塊在表示 transaction ID 時,應該既可使用自定義對象,也可使用 transaction ID。

TPC Connection Methods...

  • 🧩.tpc_begin(xid)

    使用指定的 xid (transaction ID) 來開始一個 TPC 事務。

    調用此方法時,應位於事務的外部,即自上次調用 .commit()rollback() 以來沒有執行過任何操做。

    此外,在 TPC 事務中調用 .commit().rollback() 會致使 error。若是應用程序在激活 TPC 事務後調用 .commit().rollback(),應拋出 ProgrammingError 異常。

    若是數據庫鏈接不支持 TPC,應拋出 NotSupportedError 異常。

  • 🧩.tpc_prepare()

    調用 .tpc_prepare() 開始一個 TPC 事務後,使用 .tpc_prepare() 來執行事務的第一階段。若是在 TPC 事務外部調用 .tpc_prepare(),應拋出 ProgrammingError 異常。

    在調用 .tpc_prepare() 以後,直至調用 .tpc_commit().tpc_rollback() 以前不能執行任何語句。

  • 🧩.tpc_commit([ xid ])

    以無參數形式調用 .tpc_commit() 時,該方法會提交以前使用 .tpc_prepare() 準備的 TPC 事務。

    若是在 .tpc_prepare() 以前調用 .tpc_commit(),則會執行一個單階段提交(single phase commit)。若是僅有一個資源參與到全局事務中,那麼事務管理器能夠選擇執行此操做。

    若是在調用 .tpc_commit() 時傳遞了 xid (transaction ID) ,數據庫則會提交指定的事務。若是所提供的 xid 無效,應拋出 ProgrammingError 異常。應在事務外部使用這種調用形式,用來執行恢復操做。

    該方法返回時,表示 TPC 事務已結束。

  • 🧩.tpc_rollback([ xid ])

    以無參數形式調用 .rollback() 時,該方法會回滾一個 TPC 事務。在 .tpc_prepare() 先後都可調用 .rollback()

    若是在調用 .rollback() 時傳遞了 xid (transaction ID) ,則會回滾指定事務。若是所提供的 xid 無效,應拋出 ProgrammingError 異常。應在事務外部使用這種調用形式,用來執行恢復操做。

    該方法返回時,表示 TPC 事務已結束。

  • 🧩.tpc_recover()

    返回一個適用於 .tpc_commit(xid).tpc_rollback(xid) 的列表,列表中的項由掛起的 transaction ID 組成。

    若是數據庫不支持事務恢復,應返回一個空列表,或拋出 NotSupportedError 異常。

常見的問題

在數據庫 SIG (Special Interest Groups - 特別興趣組) 中常常會重複出現一些與 DB API 規範相關的問題。本節將涵蓋一些與本規範相關的常見文通。

問: 如何從 .fetch*() 返回的元組中構造一個字典?

答: 有幾種現成的工具能夠幫你完成這項任務。這些工具大多會使用 cursor.description 屬性中定義的列名來做爲行字典的鍵。

爲何不擴展 DB API 規範,以使 fetch*() 返回字典形式的內容喃?由於這樣的作法存在如下弊端:

  • 某些數據庫的列名對大小寫不敏感,或者會自動將列名所有轉爲小寫(或大寫)字符
  • 結果集中的列是經過 SQL 查詢(例如使用 SQL 函數)產生的,它不必定與數據庫表中的列名匹配,數據庫一般會用一種數據庫特有的方式來生成列名。

所以,在不一樣的數據庫中,用來訪問列的字典的鍵是不一樣的,這使得沒法編寫具有可移植性的代碼。

1.0 和 2.0 的主要區別

相較於 1.0 版,在 2.0 中引入了一些重大改動。2.0 中的某些改動將致使現有的基於 DB API 1.0 的腳本發生終端,所以調整了主版本號以反映此更改的力度。

從 1.0 和 2.0 到最重要的變化以下:

  • 移除了對單獨的 dbi 模塊的需求,並將其包含的功能合併到模塊接口自身中。
  • 爲 date/time 值添加了新的 構造函數Type ObjectsRAW Type Object 被重命名爲 BINARY。結果集應涵蓋現代 SQL 數據庫常見的全部基本數據類型。
  • 添加了新的常量(apilevel, threadsfety, paramstyle)和方法(executemany(), nextset()),以便提供更好的數據庫綁定。
  • 如今清楚定了調用存儲過程所需的 .callproc() 的語義
  • 修改了 .execute() 返回值的定義。之前,返回值基於 SQL 語句類型,很難正確實現;如今,改成未定義。之前 .execute() 可能會返回結果集的行數,如今請使用 .rowcount。模塊能夠自由返回舊樣式返回值,但這再也不是規範的強制要求的,應該被視爲依賴於數據庫接口。
  • 基於 exceptions 的異常類被歸入規範。模塊實現者能夠經過繼承定義的異常類來自由擴展本規範中定義的異常佈局。

在 DB API 2.0 規範發佈後,添加的內容:

  • 爲核心 DB API 2.0 規提供了一組可選 DB API 擴展。

已知問題

儘管 2.0 規範中澄清了 1.0 版中還沒有解決的許多問題,但仍有一些問題須要在將來版本中解決:

  • .nextset() 的返回值應能表現出新結果集的可用狀況
  • 集成十進制模塊(decimal module) Decimal 對象,以用做無損貨幣和十進制交換格式。

歡迎關注公衆號: import hello

公衆號

相關文章
相關標籤/搜索