小巧、快速、可靠。
選擇其中三項。
SQLite 的虛擬表格機制

1. 簡介

虛擬表格是一個向已開啟的 SQLite 資料庫連線 註冊的物件。從 SQL 陳述式的角度來看,虛擬表格物件看起來像任何其他表格或檢視。但在幕後,對虛擬表格的查詢和更新會呼叫虛擬表格物件的回呼方法,而不是讀寫資料庫檔案。

虛擬表格機制允許應用程式發佈可從 SQL 陳述式存取的介面,就像它們是表格一樣。SQL 陳述式幾乎可以對虛擬表格執行任何可以對真實表格執行的操作,但以下例外:

個別的虛擬表格實作可能會施加額外的限制。例如,某些虛擬實作可能提供唯讀表格。或者,某些虛擬表格實作可能允許 INSERTDELETE 但不允許 UPDATE。或者,某些虛擬表格實作可能會限制可以進行的 UPDATE 類型。

虛擬表格可能代表記憶體中的資料結構。或者,它可能代表磁碟上非 SQLite 格式的資料檢視。或者,應用程式可能會依需求計算虛擬表格的內容。

以下是一些現有和假設的虛擬表格用途:

如需實際虛擬表格實作的更完整列表,請參閱虛擬表格列表頁面。

1.1. 用法

虛擬表格是使用 CREATE VIRTUAL TABLE 陳述式建立的。

create-virtual-table-stmt

CREATE VIRTUAL TABLE IF NOT EXISTS schema-name . table-name USING module-name ( module-argument ) ,

CREATE VIRTUAL TABLE 陳述式會建立一個名為 table-name 的新表格,該表格衍生自 module-name 類別。module-name 是由 sqlite3_create_module() 介面為虛擬表格註冊的名稱。

CREATE VIRTUAL TABLE tablename USING modulename;

也可以在模組名稱後面提供以逗號分隔的參數。

CREATE VIRTUAL TABLE tablename USING modulename(arg1, arg2, ...);

模組參數的格式非常通用。每個 module-argument 都可能包含關鍵字、字串常值、識別碼、數字和標點符號。每個 module-argument 都會在建立虛擬表格時,按原樣(以文字形式)傳遞到虛擬表格實作的 建構子方法 中,而該建構子負責解析和解讀這些參數。參數語法相當通用,因此虛擬表格實作可以根據需要將其參數解讀為一般 CREATE TABLE 陳述式中的 欄位定義。實作也可以對參數施加其他解讀方式。

建立虛擬表格後,除了上述注意事項和特定虛擬表格實作的限制之外,它的使用方法與其他表格相同。虛擬表格可以使用一般的 DROP TABLE 語法刪除。

1.1.1. 暫存虛擬表格

沒有「CREATE TEMP VIRTUAL TABLE」陳述式。要建立暫存虛擬表格,請在虛擬表格名稱前面加上「temp」綱要。

CREATE VIRTUAL TABLE temp.tablename USING module(arg1, ...);

1.1.2. 同名虛擬表格

有些虛擬表格會自動存在於每個已註冊其模組的資料庫連線的「main」綱要中,即使沒有 CREATE VIRTUAL TABLE 陳述式也是如此。這樣的虛擬表格稱為「同名虛擬表格」。要使用同名虛擬表格,只需將模組名稱當作表格名稱使用即可。同名虛擬表格只存在於「main」綱要中,因此如果在其前面加上不同的綱要名稱將無法運作。

dbstat 虛擬表格 就是同名虛擬表格的一個例子。要將 dbstat 虛擬表格用作同名虛擬表格,只需像查詢一般表格一樣查詢「dbstat」模組名稱即可。(請注意,SQLite 必須使用 SQLITE_ENABLE_DBSTAT_VTAB 選項編譯,才能將 dbstat 虛擬表格包含在建置中。)

SELECT * FROM dbstat;

如果虛擬表格的 xCreate 方法與 xConnect 方法完全相同,或者 xCreate 方法為 NULL,則該虛擬表格為同名虛擬表格。xCreate 方法會在使用 CREATE VIRTUAL TABLE 陳述式首次建立虛擬表格時呼叫。xConnect 方法會在資料庫連線附加到綱要或重新解析綱要時呼叫。當這兩個方法相同時,表示虛擬表格沒有需要建立和刪除的持續狀態。

1.1.3. 僅限同名虛擬表格

如果 xCreate 方法為 NULL,則該虛擬表格將禁止使用 CREATE VIRTUAL TABLE 陳述式,且該虛擬表格為「僅限同名虛擬表格」。僅限同名虛擬表格可用作 表格值函式

請注意,在 3.9.0 版 (2015-10-14) 之前,SQLite 在調用 xCreate 方法前並未檢查是否為 NULL。因此,如果在 SQLite 3.8.11.1 版 (2015-07-29) 或更早版本中註冊了一個僅具同名函數的虛擬表格,並嘗試對該虛擬表格模組執行 CREATE VIRTUAL TABLE 命令,則會發生跳至空指標的錯誤,導致程式崩潰。

1.2. 實作

虛擬表格的實作使用了幾個新的 C 語言層級物件。

typedef struct sqlite3_vtab sqlite3_vtab;
typedef struct sqlite3_index_info sqlite3_index_info;
typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor;
typedef struct sqlite3_module sqlite3_module;

sqlite3_module 結構定義了一個用於實作虛擬表格的模組物件。可以將模組視為一個類別,您可以從中建構多個具有相似屬性的虛擬表格。例如,您可能有一個模組,提供對磁碟上逗號分隔值 (CSV) 檔案的唯讀存取權。然後,可以使用該模組建立多個虛擬表格,其中每個虛擬表格都指向不同的 CSV 檔案。

模組結構包含由 SQLite 調用的方法,用於對虛擬表格執行各種操作,例如建立虛擬表格的新執行個體或銷毀舊執行個體、讀寫資料、搜尋和刪除、更新或插入資料列。模組結構的詳細說明如下。

每個虛擬表格執行個體都由一個 sqlite3_vtab 結構表示。 sqlite3_vtab 結構如下所示:

struct sqlite3_vtab {
  const sqlite3_module *pModule;
  int nRef;
  char *zErrMsg;
};

虛擬表格實作通常會將此結構子類別化,以新增其他私有和實作特定的欄位。 nRef 欄位由 SQLite 核心內部使用,虛擬表格實作不應更改它。虛擬表格實作可以透過將錯誤訊息字串放入 zErrMsg 中,將錯誤訊息文字傳遞給核心。儲存此錯誤訊息字串的空間必須透過 SQLite 記憶體配置函式(例如 sqlite3_mprintf()sqlite3_malloc())取得。在指派新值給 zErrMsg 之前,虛擬表格實作必須使用 sqlite3_free() 釋放 zErrMsg 中任何先前存在的內容。否則將導致記憶體洩漏。當 SQLite 核心將錯誤訊息文字傳遞給用戶端應用程式或銷毀虛擬表格時,它會釋放 zErrMsg 的內容並將其歸零。虛擬表格實作僅需在使用新的不同錯誤訊息覆寫內容時才需要擔心釋放 zErrMsg 內容。

sqlite3_vtab_cursor 結構表示指向虛擬表格特定資料列的指標。 sqlite3_vtab_cursor 的外觀如下:

struct sqlite3_vtab_cursor {
  sqlite3_vtab *pVtab;
};

同樣地,實際的實作可能會將此結構子類別化,以新增其他私有欄位。

sqlite3_index_info 結構用於將資訊傳遞到實作虛擬表格的模組的 xBestIndex 方法中,以及從中傳遞資訊。

在執行 CREATE VIRTUAL TABLE 陳述式之前,必須在資料庫連線中註冊該陳述式中指定的模組。這可以使用 sqlite3_create_module()sqlite3_create_module_v2() 介面來完成:

int sqlite3_create_module(
  sqlite3 *db,               /* SQLite connection to register module with */
  const char *zName,         /* Name of the module */
  const sqlite3_module *,    /* Methods for the module */
  void *                     /* Client data for xCreate/xConnect */
);
int sqlite3_create_module_v2(
  sqlite3 *db,               /* SQLite connection to register module with */
  const char *zName,         /* Name of the module */
  const sqlite3_module *,    /* Methods for the module */
  void *,                    /* Client data for xCreate/xConnect */
  void(*xDestroy)(void*)     /* Client data destructor function */
);

sqlite3_create_module()sqlite3_create_module_v2() 常式會將模組名稱與 sqlite3_module 結構以及特定於每個模組的用戶端資料關聯起來。兩種 create_module 方法的唯一區別是 _v2 方法包含一個額外的參數,該參數指定用戶端資料指標的解構函式。模組結構定義了虛擬表格的行為。模組結構如下所示:

struct sqlite3_module {
  int iVersion;
  int (*xCreate)(sqlite3*, void *pAux,
               int argc, char *const*argv,
               sqlite3_vtab **ppVTab,
               char **pzErr);
  int (*xConnect)(sqlite3*, void *pAux,
               int argc, char *const*argv,
               sqlite3_vtab **ppVTab,
               char **pzErr);
  int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*);
  int (*xDisconnect)(sqlite3_vtab *pVTab);
  int (*xDestroy)(sqlite3_vtab *pVTab);
  int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor);
  int (*xClose)(sqlite3_vtab_cursor*);
  int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
                int argc, sqlite3_value **argv);
  int (*xNext)(sqlite3_vtab_cursor*);
  int (*xEof)(sqlite3_vtab_cursor*);
  int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int);
  int (*xRowid)(sqlite3_vtab_cursor*, sqlite_int64 *pRowid);
  int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite_int64 *);
  int (*xBegin)(sqlite3_vtab *pVTab);
  int (*xSync)(sqlite3_vtab *pVTab);
  int (*xCommit)(sqlite3_vtab *pVTab);
  int (*xRollback)(sqlite3_vtab *pVTab);
  int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName,
                     void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
                     void **ppArg);
  int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);
  /* The methods above are in version 1 of the sqlite_module object. Those 
  ** below are for version 2 and greater. */
  int (*xSavepoint)(sqlite3_vtab *pVTab, int);
  int (*xRelease)(sqlite3_vtab *pVTab, int);
  int (*xRollbackTo)(sqlite3_vtab *pVTab, int);
  /* The methods above are in versions 1 and 2 of the sqlite_module object.
  ** Those below are for version 3 and greater. */
  int (*xShadowName)(const char*);
  /* The methods above are in versions 1 through 3 of the sqlite_module object.
  ** Those below are for version 4 and greater. */
  int (*xIntegrity)(sqlite3_vtab *pVTab, const char *zSchema,
                    const char *zTabName, int mFlags, char **pzErr);
};

模組結構定義了每個虛擬表格物件的所有方法。模組結構還包含 iVersion 欄位,該欄位定義了模組表格結構的特定版本。目前,iVersion 一律為 4 或更小,但在 SQLite 的未來版本中,模組結構定義可能會擴展其他方法,在這種情況下,最大 iVersion 值將會增加。

模組結構的其餘部分包含用於實現虛擬表格各種功能的方法。後續將提供關於每個方法作用的詳細資訊。

1.3. 虛擬表格與共享快取

在 SQLite 3.6.17 版(2009-08-10)之前,虛擬表格機制假設每個 資料庫連線 都保留自己的資料庫結構副本。因此,虛擬表格機制無法在啟用 共享快取模式 的資料庫中使用。如果啟用了 共享快取模式sqlite3_create_module() 介面將會返回錯誤。從 SQLite 3.6.17 版 開始,放寬了該限制。

1.4. 建立新的虛擬表格實作

按照以下步驟建立您自己的虛擬表格:

  1. 編寫所有必要的方法。
  2. 建立一個 sqlite3_module 結構的實例,其中包含指向步驟 1 中所有方法的指標。
  3. 使用 sqlite3_create_module()sqlite3_create_module_v2() 介面之一註冊您的 sqlite3_module 結構。
  4. 執行 CREATE VIRTUAL TABLE 命令,在 USING 子句中指定新的模組。

唯一真正困難的部分是步驟 1。您可以從現有的虛擬表格實作開始,並根據您的需求進行修改。SQLite 原始碼樹 包含許多適合複製的虛擬表格實作,包括:

SQLite 原始碼樹中還有許多其他虛擬表格實作 可以作為範例。搜尋「sqlite3_create_module」即可找到這些其他虛擬表格實作。

您也可以將新的虛擬表格實作為 可載入擴充功能

2. 虛擬表格方法

2.1. xCreate 方法

int (*xCreate)(sqlite3 *db, void *pAux,
             int argc, char *const*argv,
             sqlite3_vtab **ppVTab,
             char **pzErr);

xCreate 方法會在回應 CREATE VIRTUAL TABLE 陳述式時呼叫,以建立虛擬表格的新實例。如果 xCreate 方法與 xConnect 方法指向相同的指標,則該虛擬表格為 同名虛擬表格。如果省略了 xCreate 方法(如果它是 NULL 指標),則該虛擬表格為 僅同名虛擬表格

db 參數是一個指向正在執行 資料庫連線 的 SQLite 指標,該連線正在執行 CREATE VIRTUAL TABLE 陳述式。pAux 參數是客戶端資料指標的副本,它是註冊 虛擬表格模組sqlite3_create_module()sqlite3_create_module_v2() 呼叫的第四個參數。argv 參數是一個包含 argc 個指向以 null 結尾之字串指標的陣列。第一個字串 argv[0] 是被呼叫的模組名稱。模組名稱是作為 sqlite3_create_module() 的第二個參數和正在執行的 CREATE VIRTUAL TABLE 陳述式之 USING 子句的參數所提供的名稱。第二個字串 argv[1] 是正在建立新虛擬表格的資料庫名稱。資料庫名稱是主資料庫的「main」,或是 TEMP 資料庫的「temp」,或是附加資料庫之 ATTACH 陳述式結尾所提供的名稱。陣列的第三個元素 argv[2] 是新虛擬表格的名稱,在 CREATE VIRTUAL TABLE 陳述式中 TABLE 關鍵字之後指定。如果有的話,argv[] 陣列中的第四個和後續字串會回報 CREATE VIRTUAL TABLE 陳述式中模組名稱的參數。

此方法的工作是建構新的虛擬表格物件(一個 sqlite3_vtab 物件),並在 *ppVTab 中返回一個指向它的指標。

作為建立新的 sqlite3_vtab 結構任務的一部分,此方法必須呼叫 sqlite3_declare_vtab() 來告知 SQLite 核心虛擬表格中的欄位和資料類型。sqlite3_declare_vtab() API 具有以下原型:

int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable)

sqlite3_declare_vtab() 的第一個參數必須與此方法的第一個參數相同,即 資料庫連線 指標。 sqlite3_declare_vtab() 的第二個參數必須是一個以 null 結尾的 UTF-8 字串,其中包含格式正確的 CREATE TABLE 陳述式,該陳述式定義虛擬表格中的欄位及其資料類型。此 CREATE TABLE 陳述式中表格的名稱會被忽略,所有約束也一樣。只有欄位名稱和資料類型很重要。CREATE TABLE 陳述式字串不需要保存在永久記憶體中。一旦 sqlite3_declare_vtab() 常式返回,該字串就可以被釋放和/或重複使用。

xConnect 方法也可以選擇性地透過呼叫 sqlite3_vtab_config() 介面一次或多次來要求虛擬表格的特殊功能:

int sqlite3_vtab_config(sqlite3 *db, int op, ...);

呼叫 sqlite3_vtab_config() 是可選的。但為了最大的安全性,建議虛擬表格實作如果不在觸發程序或視圖內使用虛擬表格,則呼叫「sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY)」。

xCreate 方法不需要初始化 sqlite3_vtab 物件的 pModule、nRef 和 zErrMsg 欄位。SQLite 核心會處理這些工作。

如果成功建立新的虛擬表格,xCreate 應該返回 SQLITE_OK;如果不成功,則返回 SQLITE_ERROR。如果不成功,則不得配置 sqlite3_vtab 結構。如果失敗,則可以選擇性地在 *pzErr 中返回錯誤訊息。儲存錯誤訊息字串的空間必須使用 SQLite 記憶體配置函式(例如 sqlite3_malloc()sqlite3_mprintf())來配置,因為 SQLite 核心會在錯誤回報給應用程式後嘗試使用 sqlite3_free() 釋放該空間。

如果省略 xCreate 方法(保留為 NULL 指標),則虛擬表格為僅同名虛擬表格。無法使用 CREATE VIRTUAL TABLE 建立新的虛擬表格實例,且虛擬表格只能透過其模組名稱使用。請注意,3.9.0 (2015-10-14) 之前的 SQLite 版本無法理解僅同名虛擬表格,如果嘗試在僅同名虛擬表格上執行 CREATE VIRTUAL TABLE,則會發生區段錯誤,因為未檢查 xCreate 方法是否為 null。

如果 xCreate 方法與 xConnect 方法的指標完全相同,則表示虛擬表格不需要初始化後端儲存。這樣的虛擬表格可以作為同名虛擬表格或使用 CREATE VIRTUAL TABLE 作為具名虛擬表格,或同時作為兩者使用。

2.1.1. 虛擬表格中的隱藏欄位

如果欄位資料類型包含特殊關鍵字「HIDDEN」(大小寫字母任意組合),則該關鍵字會從欄位資料類型名稱中省略,並且該欄位會在內部標記為隱藏欄位。隱藏欄位與一般欄位在三個方面有所不同:

例如,如果將以下 SQL 傳遞給 sqlite3_declare_vtab()

CREATE TABLE x(a HIDDEN VARCHAR(12), b INTEGER, c INTEGER Hidden);

則會建立具有兩個隱藏欄位的虛擬表格,其資料類型分別為「VARCHAR(12)」和「INTEGER」。

FTS3 虛擬表格實作中可以看到隱藏欄位的用法範例,其中每個 FTS 虛擬表格都包含一個 FTS 隱藏欄位,用於將資訊從虛擬表格傳遞到 FTS 輔助函式 以及 FTS MATCH 運算子。

2.1.2. 表值函式

包含隱藏欄位虛擬表格可以在 SELECT 陳述式的 FROM 子句中像表值函式一樣使用。表值函式的引數會成為虛擬表格的隱藏欄位上的限制條件。

例如,「generate_series」擴充功能(位於原始程式碼樹中的 ext/misc/series.c 檔案中)實作了具有以下結構描述的同名虛擬表格

CREATE TABLE generate_series(
  value,
  start HIDDEN,
  stop HIDDEN,
  step HIDDEN
);

此表格實作中的 sqlite3_module.xBestIndex 方法會檢查隱藏欄位上的等式限制條件,並將這些限制條件作為輸入參數來決定要產生的整數「value」輸出的範圍。對於任何未受限制的欄位,會使用合理的預設值。例如,要列出 5 到 50 之間的所有整數

SELECT value FROM generate_series(5,50);

上述查詢等同於以下查詢

SELECT value FROM generate_series WHERE start=5 AND stop=50;

虛擬表格名稱上的引數會依序與隱藏欄位匹配。引數的數量可以少於隱藏欄位的數量,在這種情況下,後面的隱藏欄位不受限制。但是,如果引數數量多於虛擬表格中的隱藏欄位數量,則會導致錯誤。

2.1.3. WITHOUT ROWID 虛擬表格

從 SQLite 3.14.0 版 (2016-08-08) 開始,傳入 sqlite3_declare_vtab() 的 CREATE TABLE 陳述式可以包含 WITHOUT ROWID 子句。這在虛擬表格列不容易映射到唯一整數的情況下很有用。包含 WITHOUT ROWID 的 CREATE TABLE 陳述式必須定義一或多個欄作為 PRIMARY KEY (主鍵)。PRIMARY KEY 的每個欄位都必須個別設為 NOT NULL (不可為空值),且每一列的所有欄位值組合起來必須是唯一的。

請注意,SQLite 不會強制執行 WITHOUT ROWID 虛擬表格的 PRIMARY KEY。強制執行是底層虛擬表格實作的責任。但 SQLite 的確會假設 PRIMARY KEY 限制有效 — 即識別的欄位確實是 UNIQUE (唯一) 且 NOT NULL — 並使用該假設來最佳化對虛擬表格的查詢。

rowid 欄位在 WITHOUT ROWID 虛擬表格上無法存取(理所當然)。

xUpdate 方法最初的設計是圍繞著將 ROWID 作為單一值。 xUpdate 方法已擴展為可使用任意 PRIMARY KEY 來取代 ROWID,但 PRIMARY KEY 仍然只能是一個欄位。因此,SQLite 會拒絕任何具有多個 PRIMARY KEY 欄位和非 NULL xUpdate 方法的 WITHOUT ROWID 虛擬表格。

2.2. xConnect 方法

int (*xConnect)(sqlite3*, void *pAux,
             int argc, char *const*argv,
             sqlite3_vtab **ppVTab,
             char **pzErr);

xConnect 方法與 xCreate 非常相似。它具有相同的參數,並像 xCreate 一樣建構一個新的 sqlite3_vtab 結構。而且它也必須像 xCreate 一樣呼叫 sqlite3_declare_vtab()。它也應該進行與 xCreate 相同的所有 sqlite3_vtab_config() 呼叫。

不同之處在於,xConnect 是用於建立與現有虛擬表格的新連線,而 xCreate 是用於從頭建立新的虛擬表格。

xCreate 和 xConnect 方法僅在虛擬表格具有某種必須在第一次建立虛擬表格時初始化的後端儲存時才有所不同。xCreate 方法建立並初始化後端儲存。xConnect 方法僅連線到現有的後端儲存。當 xCreate 和 xConnect 相同時,該表格是一個 同名虛擬表格

舉例來說,考慮一個提供對磁碟上現有逗號分隔值 (CSV) 檔案唯讀存取的虛擬表格實作。此類虛擬表格不需要建立或初始化後端儲存(因為 CSV 檔案已存在於磁碟上),因此該模組的 xCreate 和 xConnect 方法將會相同。

另一個例子是實作全文索引的虛擬表格。xCreate 方法必須建立並初始化資料結構來保存該索引的字典和發佈清單。另一方面,xConnect 方法只需要找到並使用先前 xCreate 呼叫所建立的現有字典和發佈清單。

如果成功建立新的虛擬表格,xConnect 方法必須返回 SQLITE_OK,如果不成功則返回 SQLITE_ERROR。如果不成功,則不得配置 sqlite3_vtab 結構。如果不成功,則可以選擇在 *pzErr 中返回錯誤訊息。必須使用 SQLite 記憶體配置函式(例如 sqlite3_malloc()sqlite3_mprintf())來配置保存錯誤訊息字串的空間,因為 SQLite 核心會在將錯誤回報給應用程式後嘗試使用 sqlite3_free() 釋放該空間。

每個虛擬表格實作都需要 xConnect 方法,但如果虛擬表格不需要初始化後端儲存,則 sqlite3_module 物件的 xCreate 和 xConnect 指標可以指向同一個函式。

2.3. xBestIndex 方法

SQLite 使用虛擬表格模組的 xBestIndex 方法來決定存取虛擬表格的最佳方式。xBestIndex 方法的原型如下:

int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*);

SQLite 核心透過填入 sqlite3_index_info 結構的特定欄位,並將指向該結構的指標作為第二個參數傳遞給 xBestIndex,以此與 xBestIndex 方法進行通訊。xBestIndex 方法會填寫此結構的其他欄位以形成回覆。 sqlite3_index_info 結構如下所示:

struct sqlite3_index_info {
  /* Inputs */
  const int nConstraint;     /* Number of entries in aConstraint */
  const struct sqlite3_index_constraint {
     int iColumn;              /* Column constrained.  -1 for ROWID */
     unsigned char op;         /* Constraint operator */
     unsigned char usable;     /* True if this constraint is usable */
     int iTermOffset;          /* Used internally - xBestIndex should ignore */
  } *const aConstraint;      /* Table of WHERE clause constraints */
  const int nOrderBy;        /* Number of terms in the ORDER BY clause */
  const struct sqlite3_index_orderby {
     int iColumn;              /* Column number */
     unsigned char desc;       /* True for DESC.  False for ASC. */
  } *const aOrderBy;         /* The ORDER BY clause */

  /* Outputs */
  struct sqlite3_index_constraint_usage {
    int argvIndex;           /* if >0, constraint is part of argv to xFilter */
    unsigned char omit;      /* Do not code a test for this constraint */
  } *const aConstraintUsage;
  int idxNum;                /* Number used to identify the index */
  char *idxStr;              /* String, possibly obtained from sqlite3_malloc */
  int needToFreeIdxStr;      /* Free idxStr using sqlite3_free() if true */
  int orderByConsumed;       /* True if output is already ordered */
  double estimatedCost;      /* Estimated cost of using this index */
  /* Fields below are only available in SQLite 3.8.2 and later */
  sqlite3_int64 estimatedRows;    /* Estimated number of rows returned */
  /* Fields below are only available in SQLite 3.9.0 and later */
  int idxFlags;              /* Mask of SQLITE_INDEX_SCAN_* flags */
  /* Fields below are only available in SQLite 3.10.0 and later */
  sqlite3_uint64 colUsed;    /* Input: Mask of columns used by statement */
};

請注意關於 "estimatedRows"、"idxFlags" 和 colUsed 欄位的警告。這些欄位分別在 SQLite 3.8.2、3.9.0 和 3.10.0 版本中加入。任何讀取或寫入這些欄位的擴充功能必須先檢查正在使用的 SQLite 函式庫版本是否大於或等於適當的版本 — 也許可以將 sqlite3_libversion_number() 的返回值與常數 3008002、3009000 和/或 3010000 進行比較。嘗試存取由舊版 SQLite 建立的 sqlite3_index_info 結構中的這些欄位,其結果未定義。

此外,還有一些定義的常數:

#define SQLITE_INDEX_CONSTRAINT_EQ         2
#define SQLITE_INDEX_CONSTRAINT_GT         4
#define SQLITE_INDEX_CONSTRAINT_LE         8
#define SQLITE_INDEX_CONSTRAINT_LT        16
#define SQLITE_INDEX_CONSTRAINT_GE        32
#define SQLITE_INDEX_CONSTRAINT_MATCH     64
#define SQLITE_INDEX_CONSTRAINT_LIKE      65  /* 3.10.0 and later */
#define SQLITE_INDEX_CONSTRAINT_GLOB      66  /* 3.10.0 and later */
#define SQLITE_INDEX_CONSTRAINT_REGEXP    67  /* 3.10.0 and later */
#define SQLITE_INDEX_CONSTRAINT_NE        68  /* 3.21.0 and later */
#define SQLITE_INDEX_CONSTRAINT_ISNOT     69  /* 3.21.0 and later */
#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70  /* 3.21.0 and later */
#define SQLITE_INDEX_CONSTRAINT_ISNULL    71  /* 3.21.0 and later */
#define SQLITE_INDEX_CONSTRAINT_IS        72  /* 3.21.0 and later */
#define SQLITE_INDEX_CONSTRAINT_LIMIT     73  /* 3.38.0 and later */
#define SQLITE_INDEX_CONSTRAINT_OFFSET    74  /* 3.38.0 and later */
#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150  /* 3.25.0 and later */
#define SQLITE_INDEX_SCAN_UNIQUE           1  /* Scan visits at most 1 row */

使用 sqlite3_vtab_collation() 介面來查找在評估第 i 個約束時應使用的 排序序列 的名稱。

const char *sqlite3_vtab_collation(sqlite3_index_info*, int i);

當 SQLite 核心編譯涉及虛擬表格的查詢時,它會呼叫 xBestIndex 方法。換句話說,SQLite 在執行 sqlite3_prepare() 或其等效函式時會呼叫此方法。透過呼叫此方法,SQLite 核心向虛擬表格表明它需要存取虛擬表格中的某些列子集,並且想知道執行該存取的最有效方法。xBestIndex 方法會回覆 SQLite 核心可以用來有效搜尋虛擬表格的資訊。

在編譯單個 SQL 查詢時,SQLite 核心可能會多次呼叫 xBestIndex,並在 sqlite3_index_info 中使用不同的設定。然後,SQLite 核心會選擇看起來效能最佳的組合。

在呼叫此方法之前,SQLite 核心會使用它目前嘗試處理的查詢的資訊初始化 sqlite3_index_info 結構的實例。此資訊主要來自查詢的 WHERE 子句和 ORDER BY 或 GROUP BY 子句,如果查詢是聯結,則也來自任何 ON 或 USING 子句。SQLite 核心提供給 xBestIndex 方法的資訊保存在結構中標記為「輸入」的部分。「輸出」部分初始化為零。

sqlite3_index_info 結構中的資訊是暫時的,並且可能在 xBestIndex 方法返回後立即被覆蓋或釋放。如果 xBestIndex 方法需要記住 sqlite3_index_info 結構的任何部分,它應該建立一個副本。必須小心地將副本儲存在將被釋放的位置,例如在 idxStr 欄位中,并将 needToFreeIdxStr 設定為 1。

請注意,xBestIndex 總是在 xFilter 之前被呼叫,因為 xBestIndex 的 idxNum 和 idxStr 輸出是 xFilter 的必要輸入。但是,不能保證在 xBestIndex 成功後會呼叫 xFilter。

每個虛擬表格實作都需要 xBestIndex 方法。

2.3.1. 輸入

SQLite 核心嘗試與虛擬表格通訊的主要內容是可用於限制需要搜尋的列數的約束。aConstraint[] 陣列包含每個約束的一個項目。該陣列中將正好有 nConstraint 個項目。

每個約束通常對應於 WHERE 子句或 USING 或 ON 子句中的一個術語,其形式如下:

column OP EXPR (欄位 運算式 表達式)

其中,「column」是虛擬表格中的一個欄位,OP 是一個運算子,例如「=」或「<」,而 EXPR 是一個任意表達式。因此,例如,如果 WHERE 子句包含像這樣的術語

a = 5

則其中一個約束條件會在「a」欄位上,運算子為「=」,表達式為「5」。約束條件不需要 WHERE 子句的字面表示。查詢最佳化器可能會對 WHERE 子句進行轉換,以便提取盡可能多的約束條件。因此,例如,如果 WHERE 子句包含如下內容

x BETWEEN 10 AND 100 AND 999>y

查詢最佳化器可能會將其轉換為三個單獨的約束條件

x >= 10
x <= 100
y < 999

對於每個這樣的約束條件,aConstraint[].iColumn 欄位指示哪個欄位出現在約束條件的左側。虛擬表格的第一個欄位是欄位 0。虛擬表格的 rowid 是欄位 -1。aConstraint[].op 欄位指示使用了哪個運算子。SQLITE_INDEX_CONSTRAINT_* 常數將整數常數映射到運算子值。欄位的出現順序與在 sqlite3_declare_vtab()xCreatexConnect 方法中定義的順序相同。判斷欄位索引時會計算隱藏欄位。

如果定義了虛擬表格的 xFindFunction() 方法,並且 xFindFunction() 有時返回 SQLITE_INDEX_CONSTRAINT_FUNCTION 或更大值,則約束條件也可能採用以下形式

FUNCTION( column, EXPR)

在這種情況下,aConstraint[].op 值與 xFindFunction() 針對 FUNCTION 返回的值相同。

aConstraint[] 陣列包含適用於虛擬表格的所有約束條件的資訊。但由於聯結中表格的排序方式,某些約束條件可能無法使用。因此,xBestIndex 方法必須只考慮 aConstraint[].usable 旗標為 true 的約束條件。

除了 WHERE 子句約束條件外,SQLite 核心還會告知 xBestIndex 方法 ORDER BY 子句的相關資訊。(在聚合查詢中,SQLite 核心可能會將 GROUP BY 子句資訊取代 ORDER BY 子句資訊,但此事實不應對 xBestIndex 方法造成任何影響。)如果 ORDER BY 子句的所有術語都是虛擬表格中的欄位,則 nOrderBy 將是 ORDER BY 子句中的術語數,而 aOrderBy[] 陣列將識別排序子句中每個術語的欄位,以及該欄位是 ASC 還是 DESC。

在 SQLite 3.10.0 版(2016-01-06)及之後的版本中,可以使用 colUsed 欄位來指示準備語句實際使用了虛擬表格的哪些欄位。如果 colUsed 的最低有效位元被設定,則表示使用了第一個欄位。次低有效位元對應於第二個欄位,依此類推。如果 colUsed 的最高有效位元被設定,則表示使用了 63 個欄位以外的一個或多個欄位。如果 xFilter 方法需要欄位使用資訊,則必須將所需的位元編碼到輸出 idxNum 欄位或 idxStr 內容中。

2.3.1.1. LIKE、GLOB、REGEXP 和 MATCH 函式

對於 LIKE、GLOB、REGEXP 和 MATCH 運算子,aConstraint[].iColumn 值是運算子左側運算元的虛擬表格欄位。但是,如果這些運算子表示為函式呼叫而不是運算子,則 aConstraint[].iColumn 值會參考作為該函式第二個引數的虛擬表格欄位

LIKE(EXPR, column)
GLOB(EXPR, column)
REGEXP(EXPR, column)
MATCH(EXPR, column)

因此,就 xBestIndex() 方法而言,以下兩種形式是等效的

column LIKE EXPR
LIKE(EXPR,column)

只有 LIKE、GLOB、REGEXP 和 MATCH 函數會參考函數的第二個參數。對於所有其他函數,aConstraint[].iColumn 值會參考函數的第一個參數。

然而,LIKE、GLOB、REGEXP 和 MATCH 的這個特殊功能並不適用於 xFindFunction() 方法。 xFindFunction() 方法總是依據 LIKE、GLOB、REGEXP 或 MATCH 運算式的左運算元進行鍵值比對,但對於這些運算式的函數呼叫等效項,則依據第一個參數進行鍵值比對。

2.3.1.2. LIMIT 和 OFFSET

當 aConstraint[].op 為 SQLITE_INDEX_CONSTRAINT_LIMIT 或 SQLITE_INDEX_CONSTRAINT_OFFSET 之一時,表示使用虛擬資料表的 SQL 查詢語句中存在 LIMIT 或 OFFSET 子句。LIMIT 和 OFFSET 運算式沒有左運算元,因此當 aConstraint[].op 為 SQLITE_INDEX_CONSTRAINT_LIMIT 或 SQLITE_INDEX_CONSTRAINT_OFFSET 之一時,aConstraint[].iColumn 值無意義,不應使用。

2.3.1.3. 限制式的右側值

可以使用 sqlite3_vtab_rhs_value() 介面嘗試存取限制式的右運算元。但是,在執行 xBestIndex 方法時,右運算元的值可能未知,因此 sqlite3_vtab_rhs_value() 呼叫可能不會成功。通常,只有在輸入 SQL 中將限制式的右運算元編碼為字面值時,xBestIndex 才能存取它。如果右運算元編碼為表達式或主機參數,xBestIndex 可能無法存取它。某些運算式,例如 SQLITE_INDEX_CONSTRAINT_ISNULLSQLITE_INDEX_CONSTRAINT_ISNOTNULL 沒有右運算元。 sqlite3_vtab_rhs_value() 介面對於此類運算式總是返回 SQLITE_NOTFOUND

2.3.2. 輸出

根據以上所有資訊,xBestIndex 方法的工作是找出搜尋虛擬資料表的最佳方式。

xBestIndex 方法透過 idxNum 和 idxStr 欄位將索引策略傳達給 xFilter 方法。就 SQLite 核心而言,idxNum 值和 idxStr 字串內容是任意的,可以具有任何意義,只要 xBestIndex 和 xFilter 就其意義達成一致即可。SQLite 核心只是將資訊從 xBestIndex 複製到 xFilter 方法,僅假設透過 idxStr 參考的字元序列是以 NUL 結尾。

idxStr 值可以是從 SQLite 記憶體配置函數(例如 sqlite3_mprintf())取得的字串。在這種情況下,必須將 needToFreeIdxStr 旗標設定為 true,以便 SQLite 核心知道在完成使用後對該字串呼叫 sqlite3_free(),從而避免記憶體洩漏。 idxStr 值也可以是靜態常數字串,在這種情況下,needToFreeIdxStr 布林值應保持為 false。

estimatedCost 欄位應設定為針對虛擬資料表執行此查詢所需的估計磁碟存取操作次數。SQLite 核心通常會多次使用不同的限制式呼叫 xBestIndex,取得多個成本估計,然後選擇估計值最低的查詢計劃。SQLite 核心在呼叫 xBestIndex 之前會將 estimatedCost 初始化為一個非常大的值,因此如果 xBestIndex 判斷目前的參數組合不理想,它可以保持 estimatedCost 欄位不變,以阻止其使用。

如果目前的 SQLite 版本為 3.8.2 或更高版本,則 estimatedRows 欄位可以設定為建議查詢計劃返回的估計列數。如果未明確設定此值,則使用預設估計值 25 列。

如果目前的 SQLite 版本是 3.9.0 或更高版本,則 idxFlags 欄位可以設定為 SQLITE_INDEX_SCAN_UNIQUE,表示在給定輸入約束條件下,虛擬表格只會返回零或一行。在之後的 SQLite 版本中,idxFlags 欄位的其他位元可能會有不同的解釋。

aConstraintUsage[] 陣列包含 sqlite3_index_info 結構輸入區段中 nConstraint 約束條件的每個元素。xBestIndex 使用 aConstraintUsage[] 陣列來告知核心它如何使用這些約束條件。

xBestIndex 方法可以將 aConstraintUsage[].argvIndex 項目設定為大於零的值。應該將一個項目設定為 1,另一個項目設定為 2,再一個項目設定為 3,依此類推,最多可以設定 xBestIndex 方法所需的數量。對應約束條件的 EXPR 將作為 argv[] 參數傳遞給 xFilter。

例如,如果 aConstraint[3].argvIndex 設定為 1,則在呼叫 xFilter 時,傳遞給 xFilter 的 argv[0] 將具有 aConstraint[3] 約束條件的 EXPR 值。

2.3.2.1. 在位元組碼中省略約束條件檢查

預設情況下,SQLite 會產生 位元組碼,它會重複檢查虛擬表格每一行上的所有約束條件,以驗證它們是否滿足。如果虛擬表格可以保證約束條件始終會滿足,它可以嘗試透過設定 aConstraintUsage[].omit 來抑制這種重複檢查。然而,除了一些例外情況,這只是一個提示,並不保證會抑制約束條件的冗餘檢查。重點:

2.3.2.2. ORDER BY 和 orderByConsumed

如果虛擬表格將按照 ORDER BY 子句指定的順序輸出行,則 orderByConsumed 旗標可以設定為 true。如果輸出不是自動按照正確的順序排列,則 orderByConsumed 必須保留其預設的 false 設定。這將向 SQLite 核心指示,它需要在資料從虛擬表格輸出後,對資料進行單獨的排序傳遞。設定 orderByConsumed 是一種優化。如果 orderByConsumed 保留其預設值 (0),查詢將始終獲得正確的答案。如果設定了 orderByConsumed,則可以避免不必要的排序操作,從而加快查詢速度,但錯誤地設定 orderByConsumed 可能會導致錯誤的答案。建議新的虛擬表格實作最初不要設定 orderByConsumed 值,然後在確認其他所有功能都正常運作後,再嘗試透過在適當的情況下設定 orderByConsumed 來進行優化。

即使虛擬表格的輸出並非嚴格按照 nOrderBy 和 aOrderBy 指定的順序排列,有時也可以安全地設定 orderByConsumed 旗標。如果 sqlite3_vtab_distinct() 介面返回 1 或 2,則表示可以放寬排序。有關更多資訊,請參閱 sqlite3_vtab_distinct() 的說明文件。

2.3.3. 回傳值

xBestIndex 方法在成功時應該返回 SQLITE_OK。如果發生任何致命的錯誤,則應該返回適當的錯誤碼(例如:SQLITE_NOMEM)。

如果 xBestIndex 返回 SQLITE_CONSTRAINT,這並不表示錯誤。相反地,SQLITE_CONSTRAINT 表示指定的輸入參數組合不足以讓虛擬表格執行其工作。這在邏輯上等同於將 estimatedCost 設定為無限大。如果針對特定查詢計劃的每次 xBestIndex 呼叫都返回 SQLITE_CONSTRAINT,則表示無法安全地使用虛擬表格,且 sqlite3_prepare() 呼叫將因「無查詢解決方案」錯誤而失敗。

2.3.4. 強制執行表格值函數的必要參數

xBestIndex 返回的 SQLITE_CONSTRAINT 對於具有必要參數的 表格值函數 很有用。如果其中一個必要參數的 aConstraint[].usable 欄位為 false,則 xBestIndex 方法應返回 SQLITE_CONSTRAINT。如果 aConstraint[] 陣列中完全沒有出現必要欄位,則表示輸入 SQL 中省略了對應的參數。在這種情況下,xBestIndex 應在 pVTab->zErrMsg 中設定錯誤訊息,並返回 SQLITE_ERROR。總結如下:

  1. 必要參數的 aConstraint[].usable 值為 false返回 SQLITE_CONSTRAINT。

  2. aConstraint[] 陣列中任何位置都沒有出現必要參數在 pVTab->zErrMsg 中設定錯誤訊息,並返回 SQLITE_ERROR

以下範例將更好地說明 SQLITE_CONSTRAINT 作為 xBestIndex 返回值的用法

SELECT * FROM realtab, tablevaluedfunc(realtab.x);

假設「tablevaluedfunc」的第一個隱藏欄位是「param1」,則上述查詢在語義上等同於以下查詢

SELECT * FROM realtab, tablevaluedfunc
 WHERE tablevaluedfunc.param1 = realtab.x;

查詢規劃器必須決定此查詢的許多可能實作方式,但其中兩個計劃特別值得注意

  1. 掃描 realtab 的所有列,並針對每一列,在 tablevaluedfunc 中尋找 param1 等於 realtab.x 的列

  2. 掃描 tablevaluedfunc 的所有列,並針對每一列,在 realtab 中尋找 x 等於 tablevaluedfunc.param1 的列。

針對上述每個潛在計劃,都會呼叫一次 xBestIndex 方法。對於計劃 1,param1 欄位上 SQLITE_CONSTRAINT_EQ 約束的 aConstraint[].usable 旗標將為 true,因為「param1 = ?」約束的右側值將會已知,因為它是由外部 realtab 迴圈決定的。但對於計劃 2,由於右側值是由內部迴圈決定,因此是一個未知量,「param1 = ?」的 aConstraint[].usable 旗標將為 false。由於 param1 是表格值函數的必要輸入,因此當呈現計劃 2 時,xBestIndex 方法應返回 SQLITE_CONSTRAINT,表示缺少必要的輸入。這會強制查詢規劃器選擇計劃 1。

2.4. xDisconnect 方法

int (*xDisconnect)(sqlite3_vtab *pVTab);

此方法會釋放與虛擬表格的連線。只有 sqlite3_vtab 物件會被銷毀。虛擬表格不會被銷毀,且與虛擬表格關聯的任何後端儲存空間都會保留。此方法會復原 xConnect 的工作。

此方法是用於銷毀與虛擬表格連線的解構函式。將此方法與 xDestroy 作比較。xDestroy 是整個虛擬表格的解構函式。

每個虛擬表格實作都需要 xDisconnect 方法,但如果 xDisconnect 和 xDestroy 方法是同一個函數,且對特定虛擬表格有意義,則可以接受。

2.5. xDestroy 方法

int (*xDestroy)(sqlite3_vtab *pVTab);

此方法會釋放與虛擬表格的連線,就像 xDisconnect 方法一樣,它也會銷毀底層表格實作。此方法會復原 xCreate 的工作。

每當使用虛擬表格的資料庫連線關閉時,都會呼叫 xDisconnect 方法。只有在針對虛擬表格執行 DROP TABLE 陳述式時,才會呼叫 xDestroy 方法。

每個虛擬表格實作都必須要有 xDestroy 方法,但如果對於特定的虛擬表格來說合理的話,xDisconnect 和 xDestroy 方法可以使用同一個函式。

2.6. xOpen 方法

int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor);

xOpen 方法會建立一個新的游標,用於存取(讀取和/或寫入)虛擬表格。成功呼叫此方法會配置 sqlite3_vtab_cursor(或其子類別)的記憶體,初始化新的物件,並讓 *ppCursor 指向新的物件。成功的呼叫會返回 SQLITE_OK

對於每次成功呼叫此方法,SQLite 核心稍後會呼叫 xClose 方法來銷毀已配置的游標。

xOpen 方法不需要初始化 sqlite3_vtab_cursor 結構的 pVtab 欄位。SQLite 核心會自動處理這項工作。

虛擬表格實作必須能夠支援任意數量的同時開啟的游標。

游標初始開啟時處於未定義狀態。SQLite 核心會在任何嘗試定位或讀取游標之前,先在游標上呼叫 xFilter 方法。

每個虛擬表格實作都必須要有 xOpen 方法。

2.7. xClose 方法

int (*xClose)(sqlite3_vtab_cursor*);

xClose 方法會關閉先前由 xOpen 開啟的游標。SQLite 核心會針對每個使用 xOpen 開啟的游標呼叫一次 xClose。

此方法必須釋放由對應的 xOpen 呼叫所配置的所有資源。即使此方法返回錯誤,也不會再次呼叫它。SQLite 核心在關閉 sqlite3_vtab_cursor 後,將不會再次使用它。

每個虛擬表格實作都必須要有 xClose 方法。

2.8. xEof 方法

int (*xEof)(sqlite3_vtab_cursor*);

如果指定的游標目前指向有效的資料列,xEof 方法必須返回 false(零);否則返回 true(非零)。這個方法會在每次呼叫 xFilterxNext 之後立即由 SQL 引擎呼叫。

每個虛擬表格實作都必須要有 xEof 方法。

2.9. xFilter 方法

int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
              int argc, sqlite3_value **argv);

此方法開始搜尋虛擬表格。第一個參數是由 xOpen 開啟的游標。接下來的兩個參數定義了先前由 xBestIndex 選擇的特定搜尋索引。idxNum 和 idxStr 的具體含義並不重要,只要 xFilter 和 xBestIndex 對其含義達成一致即可。

xBestIndex 函式可能已使用 sqlite3_index_info 結構的 aConstraintUsage[].argvIndex 值請求某些表達式的值。這些值會使用 argc 和 argv 參數傳遞給 xFilter。

如果虛擬表格包含一或多個符合搜尋條件的資料列,則游標必須停留在第一個資料列。後續呼叫 xEof 必須返回 false(零)。如果沒有符合的資料列,則游標必須處於會導致 xEof 返回 true(非零)的狀態。SQLite 引擎將使用 xColumnxRowid 方法來存取該資料列的內容。xNext 方法將用於前進到下一個資料列。

如果成功,此方法必須返回 SQLITE_OK;如果發生錯誤,則返回 sqlite 錯誤碼

每個虛擬表格實作都必須要有 xFilter 方法。

2.10. xNext 方法

int (*xNext)(sqlite3_vtab_cursor*);

xNext 方法會將 虛擬表格游標 推進到由 xFilter 初始化的結果集的下一列。如果呼叫此例程時游標已指向最後一列,則游標不再指向有效資料,且後續呼叫 xEof 方法必須返回 true(非零)。如果游標成功推進到另一列內容,則後續呼叫 xEof 必須返回 false(零)。

如果成功,此方法必須返回 SQLITE_OK;如果發生錯誤,則返回 sqlite 錯誤碼

每個虛擬表格實作都必須要有 xNext 方法。

2.11. xColumn 方法

int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int N);

SQLite 核心會呼叫此方法來尋找目前列第 N 個欄位的值。N 從零開始,因此第一個欄位的編號為 0。xColumn 方法可以使用以下其中一個介面將其結果返回給 SQLite:

如果 xColumn 方法實作未呼叫上述任何函式,則欄位值預設為 SQL NULL。

要引發錯誤,xColumn 方法應使用其中一個 result_text() 方法來設定錯誤訊息文字,然後返回適當的 錯誤碼。xColumn 方法在成功時必須返回 SQLITE_OK

每個虛擬表格實作都必須要有 xColumn 方法。

2.12. xRowid 方法

int (*xRowid)(sqlite3_vtab_cursor *pCur, sqlite_int64 *pRowid);

成功呼叫此方法會導致 *pRowid 填入 虛擬表格游標 pCur 目前指向的列的 列ID (rowid)。此方法在成功時返回 SQLITE_OK。失敗時則返回適當的 錯誤碼

每個虛擬表格實作都必須要有 xRowid 方法。

2.13. xUpdate 方法

int (*xUpdate)(
  sqlite3_vtab *pVTab,
  int argc,
  sqlite3_value **argv,
  sqlite_int64 *pRowid
);

所有對虛擬表格的變更都使用 xUpdate 方法進行。這個方法可用於插入、刪除或更新。

argc 參數指定 argv 陣列中的項目數。對於純刪除操作,argc 的值將為 1;對於插入、取代或更新操作,argc 的值將為 N+2,其中 N 是表格中的欄位數。在上一句中,N 包括任何隱藏的欄位。

每個 argv 項目在 C 語言中都將具有非 NULL 值,但可能包含 SQL 值 NULL。換句話說,argv[i]!=0對於 0 到argc-1之間的 i 恆成立。然而,sqlite3_value_type(argv[i])==SQLITE_NULL.

的情況可能發生。argv[0] 參數是要刪除的虛擬表格中列的 列ID (rowid)。如果 argv[0] 是 SQL NULL,則不會進行刪除。

argv[1] 參數是要插入虛擬表格的新列的列 ID。如果 argv[1] 是 SQL NULL,則實作必須為新插入的列選擇一個列 ID。後續的 argv[] 項目包含虛擬表格欄位的值,順序與宣告欄位的順序相同。欄位數將與 xConnectxCreate 方法使用 sqlite3_declare_vtab() 呼叫所做的表格宣告相符。所有隱藏的欄位都包含在內。

在使用 ROWID 的虛擬表格(但不是WITHOUT ROWID 虛擬表格)上執行不帶 rowid 的插入操作(argc>1,argv[1] 為 SQL NULL)時,實作必須將 *pRowid 設定為新插入列的 rowid;這將成為 sqlite3_last_insert_rowid() 函式返回的值。在所有其他情況下設定此值是無害的空操作;如果 argc==1 或 argv[1] 不是 SQL NULL,SQLite 引擎會忽略 *pRowid 返回值。

每次呼叫 xUpdate 都會屬於以下所示的情況之一。注意,對 argv[i] 的引用指的是 argv[i] 物件中保存的 SQL 值,而不是 argv[i] 物件本身。

argc = 1
argv[0] ≠ NULL

DELETE:刪除 rowid 或 PRIMARY KEY 等於 argv[0] 的單一行。不會發生插入操作。

argc > 1
argv[0] = NULL

INSERT:插入新的一行,欄位值取自 argv[2] 及後續參數。在 rowid 虛擬表格中,如果 argv[1] 是 SQL NULL,則會自動產生新的唯一 rowid。對於 WITHOUT ROWID 虛擬表格,argv[1] 將為 NULL,在此情況下,實作應該從 argv[2] 及後續參數中的適當欄位取得 PRIMARY KEY 值。

argc > 1
argv[0] ≠ NULL
argv[0] = argv[1]

UPDATE:使用 argv[2] 及後續參數中的新值更新 rowid 或 PRIMARY KEY 為 argv[0] 的那一行。

argc > 1
argv[0] ≠ NULL
argv[0] ≠ argv[1]

帶有 rowid 或 PRIMARY KEY 變更的 UPDATE:使用 argv[1] 中的 rowid 或 PRIMARY KEY 以及 argv[2] 及後續參數中的新值更新 rowid 或 PRIMARY KEY 為 argv[0] 的那一行。當 SQL 陳述式更新 rowid 時會發生這種情況,例如以下陳述式:

UPDATE table SET rowid=rowid+1 WHERE ...;

xUpdate 方法必須且僅在其成功時返回 SQLITE_OK。如果發生失敗,xUpdate 必須返回適當的 錯誤代碼。失敗時,可以使用從 SQLite 分配的記憶體中儲存的錯誤訊息文字(使用諸如 sqlite3_mprintf()sqlite3_malloc() 之類的函式)選擇性地替換 pVTab->zErrMsg 元素。

如果 xUpdate 方法違反了虛擬表格的某些約束(包括但不限於嘗試儲存錯誤資料類型的值、嘗試儲存太大或太小的值,或嘗試更改唯讀值),則 xUpdate 必須以適當的 錯誤代碼 失敗。

如果 xUpdate 方法正在執行 UPDATE,則可以使用 sqlite3_value_nochange(X) 來發現虛擬表格的哪些欄位實際上被 UPDATE 陳述式修改了。sqlite3_value_nochange(X) 介面會針對未更改的欄位返回 true。在每次 UPDATE 時,SQLite 都會先針對表格中每個未更改的欄位分別呼叫 xColumn 以取得該欄位的值。xColumn 方法可以透過呼叫 sqlite3_vtab_nochange() 來檢查該欄位在 SQL 層級是否未更改。如果 xColumn 發現欄位未被修改,則應在不使用任何 sqlite3_result_xxxxx() 介面設定結果的情況下返回。只有在這種情況下,sqlite3_value_nochange() 在 xUpdate 方法中才會是 true。如果 xColumn 確實呼叫了一個或多個 sqlite3_result_xxxxx() 介面,則 SQLite 會將其理解為欄位值的更改,並且 xUpdate 中針對該欄位的 sqlite3_value_nochange() 呼叫將返回 false。

當 xUpdate 方法被調用時,可能有一個或多個 sqlite3_vtab_cursor 物件在虛擬表格實例上開啟並使用中,甚至可能在虛擬表格的該列上使用中。 xUpdate 的實作必須準備好處理從其他現有游標刪除或修改表格列的嘗試。如果虛擬表格無法容納此類更改,xUpdate 方法必須返回一個 錯誤碼

xUpdate 方法是選用的。如果虛擬表格的 sqlite3_module 中的 xUpdate 指針為 NULL 指針,則虛擬表格為唯讀。

2.14. xFindFunction 方法

int (*xFindFunction)(
  sqlite3_vtab *pVtab,
  int nArg,
  const char *zName,
  void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
  void **ppArg
);

sqlite3_prepare() 期間會呼叫此方法,讓虛擬表格實作有機會覆載函式。此方法可以設定為 NULL,在這種情況下不會發生覆載。

當函式使用虛擬表格中的欄位作為其第一個參數時,會呼叫此方法以查看虛擬表格是否想要覆載該函式。前三個參數是輸入:虛擬表格、函式的參數數量和函式的名稱。如果不需要覆載,此方法返回 0。要覆載函式,此方法將新的函式實作寫入 *pxFunc,將使用者資料寫入 *ppArg,並返回 1 或 SQLITE_INDEX_CONSTRAINT_FUNCTION 到 255 之間的數字。

過去,xFindFunction() 的返回值為零或一。零表示函式未被覆載,一表示函式已被覆載。在版本 3.25.0 (2018-09-15) 中添加了返回 SQLITE_INDEX_CONSTRAINT_FUNCTION 或更大值的功能。如果 xFindFunction 返回 SQLITE_INDEX_CONSTRAINT_FUNCTION 或更大值,則表示該函式接受兩個參數,並且該函式可以在查詢的 WHERE 子句中用作布林值,並且虛擬表格能夠利用該函式來加速查詢結果。當 xFindFunction 返回 SQLITE_INDEX_CONSTRAINT_FUNCTION 或更大值時,返回的值會成為傳遞到 xBestIndex() 的其中一個約束的 sqlite3_index_info.aConstraint.op 值。函式的第一個參數是由約束的 aConstraint[].iColumn 欄位識別的欄位,函式的第二個參數是將傳遞到 xFilter() 的值(如果設定了 aConstraintUsage[].argvIndex 值)或從 sqlite3_vtab_rhs_value() 返回的值。

Geopoly 模組 是一個使用 SQLITE_INDEX_CONSTRAINT_FUNCTION 來提高效能的虛擬表格範例。Geopoly 的 xFindFunction() 方法針對 geopoly_overlap() SQL 函式返回 SQLITE_INDEX_CONSTRAINT_FUNCTION,針對 geopoly_within() SQL 函式返回 SQLITE_INDEX_CONSTRAINT_FUNCTION+1。這允許對諸如以下的查詢進行搜尋最佳化:

SELECT * FROM geopolytab WHERE geopoly_overlap(_shape, $query_polygon);
SELECT * FROM geopolytab WHERE geopoly_within(_shape, $query_polygon);

請注意,中綴函式(LIKEGLOBREGEXPMATCH)會反轉其參數的順序。因此,「like(A,B)」通常與「B like A」的工作方式相同。但是,xFindFunction() 總是查看最左邊的參數,而不是第一個邏輯參數。因此,對於「B like A」的形式,SQLite 會查看左運算元「B」,如果該運算元是虛擬表格欄位,它會在該虛擬表格上調用 xFindFunction() 方法。但是,如果改用「like(A,B)」的形式,則 SQLite 會檢查 A 項是否為虛擬表格的欄位,如果是,它會調用 A 欄位虛擬表格的 xFindFunction() 方法。

此例程返回的函數指標必須在第一個參數給定的 sqlite3_vtab 物件的生命週期內保持有效。

2.15. xBegin 方法

int (*xBegin)(sqlite3_vtab *pVTab);

此方法在虛擬表格上開始一個事務。這個方法是可選的。sqlite3_module 的 xBegin 指標可以是 NULL。

此方法之後總是會接著呼叫 xCommitxRollback 方法。虛擬表格事務不允許巢狀,因此在單個虛擬表格上,如果沒有介於 xCommitxRollback 的呼叫,xBegin 方法不會被呼叫多次。在 xBegin 和對應的 xCommitxRollback 之間,可以而且很可能會發生對其他方法的多個呼叫。

2.16. xSync 方法

int (*xSync)(sqlite3_vtab *pVTab);

此方法標示在虛擬表格上開始兩階段提交。這個方法是可選的。sqlite3_module 的 xSync 指標可以是 NULL。

此方法僅在呼叫 xBegin 方法之後以及 xCommitxRollback 之前呼叫。為了實現兩階段提交,在呼叫任何虛擬表格的 xCommit 方法之前,會先呼叫所有虛擬表格的 xSync 方法。如果任何 xSync 方法失敗,則整個事務將會回滾。

2.17. xCommit 方法

int (*xCommit)(sqlite3_vtab *pVTab);

此方法使虛擬表格事務提交。這個方法是可選的。sqlite3_module 的 xCommit 指標可以是 NULL。

對此方法的呼叫總是在先前呼叫 xBeginxSync 之後。

2.18. xRollback 方法

int (*xRollback)(sqlite3_vtab *pVTab);

此方法使虛擬表格事務回滾。這個方法是可選的。sqlite3_module 的 xRollback 指標可以是 NULL。

對此方法的呼叫總是在先前呼叫 xBegin 之後。

2.19. xRename 方法

int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);

此方法通知虛擬表格的實作,該虛擬表格將被賦予一個新的名稱。如果此方法返回 SQLITE_OK,則 SQLite 會重新命名該表格。如果此方法返回 錯誤碼,則會阻止重新命名。

xRename 方法是可選的。如果省略,則無法使用 ALTER TABLE RENAME 命令重新命名虛擬表格。

在呼叫此方法之前會啟用 PRAGMA legacy_alter_table 設定,並且在此方法完成後會恢復 legacy_alter_table 的值。這對於使用 影子表格 的虛擬表格的正確運作是必要的,其中影子表格必須重新命名以匹配新的虛擬表格名稱。如果 legacy_alter_format 關閉,則每次 xRename 方法嘗試更改影子表格的名稱時,都會呼叫虛擬表格的 xConnect 方法。

2.20. xSavepoint、xRelease 和 xRollbackTo 方法

int (*xSavepoint)(sqlite3_vtab *pVtab, int);
int (*xRelease)(sqlite3_vtab *pVtab, int);
int (*xRollbackTo)(sqlite3_vtab *pVtab, int);

這些方法讓虛擬表格的實作有機會實現巢狀事務。它們始終是可選的,並且僅在 SQLite 3.7.7 版 (2011-06-23) 和之後的版本中才會被呼叫。

當呼叫 xSavepoint(X,N) 時,這是向虛擬表格 X 發出的一個信號,表示它應該將其當前狀態儲存為儲存點 N。後續呼叫 xRollbackTo(X,R) 表示虛擬表格的狀態應該返回到上次呼叫 xSavepoint(X,R) 時的狀態。呼叫 xRollbackTo(X,R) 將會使所有 N>R 的儲存點失效;任何失效的儲存點在未先透過呼叫 xSavepoint() 重新初始化之前,都不會被回滾或釋放。呼叫 xRelease(X,M) 會使所有 N>=M 的儲存點失效。

除了在 xBegin() 與 xCommit() 或 xRollback() 之間的呼叫外,xSavepoint()、xRelease() 或 xRollbackTo() 方法都不會被呼叫。

2.21. xShadowName 方法

一些虛擬表格實作(例如:FTS3FTS5RTREE)使用真實的(非虛擬的)資料庫表格來儲存內容。例如,當內容插入 FTS3 虛擬表格時,資料最終會儲存在名為 "%_content"、"%_segdir"、"%_segments"、"%_stat" 和 "%_docsize" 的真實表格中,其中 "%" 是原始虛擬表格的名稱。這些儲存虛擬表格內容的輔助真實表格稱為「影子表格」。請參閱 (1)、(2) 和 (3) 以取得更多資訊。

xShadowName 方法的存在是為了讓 SQLite 能夠判斷某個真實表格是否實際上是虛擬表格的影子表格。

如果以下所有條件都成立,SQLite 就會將一個真實表格視為影子表格:

如果 SQLite 將一個表格識別為影子表格,並且設定了 SQLITE_DBCONFIG_DEFENSIVE 旗標,則對於一般的 SQL 陳述式,該影子表格將是唯讀的。影子表格仍然可以被寫入,但只能透過從某些虛擬表格實作的方法中呼叫的 SQL 進行寫入。

xShadowName 方法的重點在於保護影子表格的內容不被惡意 SQL 破壞。每個使用影子表格的虛擬表格實作都應該能夠偵測並處理損毀的影子表格內容。然而,特定虛擬表格實作中的錯誤可能會讓蓄意損毀的影子表格導致程式崩潰或其他故障。 xShadowName 機制旨在透過防止一般的 SQL 陳述式蓄意損毀影子表格來避免零時差攻擊。

影子表格預設為可讀寫。只有在使用 sqlite3_db_config() 設定 SQLITE_DBCONFIG_DEFENSIVE 旗標時,影子表格才會變成唯讀。為了維持向後相容性,影子表格預設需要是可讀寫的。例如,命令列介面.dump 命令產生的 SQL 文字會直接寫入影子表格。

2.22. xIntegrity 方法

如果 sqlite3_module 的 iVersion 為 4 或更高版本,且 xIntegrity 方法不為 NULL,則 PRAGMA integrity_checkPRAGMA quick_check 命令將在處理過程中呼叫 xIntegrity。如果 xIntegrity 方法將錯誤訊息字串寫入第五個參數,則 PRAGMA integrity_check 將在其輸出中回報該錯誤。換句話說,xIntegrity 方法允許 PRAGMA integrity_check 命令驗證儲存在虛擬表格中的內容的完整性。

xIntegrity 方法使用五個參數呼叫:

xIntegrity 方法通常應該返回 SQLITE_OK - 即使它在虛擬表格的內容中發現問題。任何其他錯誤碼都表示 xIntegrity 方法本身在嘗試評估虛擬表格內容時遇到問題。因此,例如,如果發現 FTS5 的倒排索引內部不一致,則 xIntegrity 方法應將適當的錯誤訊息寫入 pzErr 參數並返回 SQLITE_OK。但如果 xIntegrity 方法由於記憶體不足而無法完成對虛擬表格內容的評估,則它應該返回 SQLITE_NOMEM。

如果產生錯誤訊息,則應透過 sqlite3_malloc64() 或等效函數取得存放錯誤訊息字串的空間。當 xIntegrity 返回時,錯誤訊息字串的所有權將轉移給 SQLite 核心。核心將確保調用 sqlite3_free() 來回收已使用完的錯誤訊息的記憶體。調用 xIntegrity 方法的 PRAGMA integrity_check 命令不會更改返回的錯誤訊息。xIntegrity 方法本身應將虛擬表格的名稱包含在訊息中。提供 zSchema 和 zName 參數是為了方便這麼做。

mFlags 參數目前是一個布林值(0 或 1),指示 xIntegrity 方法是由於 PRAGMA integrity_check (mFlags==0) 還是 PRAGMA quick_check (mFlags==1) 而被調用。一般來說,xIntegrity 方法無論如何都應該執行它可以在線性時間內完成的任何有效性檢查,但只有在(mFlags&1)==0的情況下才執行需要超線性時間的檢查。未來版本的 SQLite 可能會使用 mFlags 參數的高位來指示其他處理選項。

對 xIntegrity 方法的支援已在 SQLite 3.44.0 版(2023-11-01)中新增。在同一個版本中,xIntegrity 方法也被新增到許多內建的虛擬表格中,例如 FTS3FTS5RTREE,以便在執行 PRAGMA integrity_check 時自動檢查這些表格內容的一致性。

本頁面最後修改時間:2024-05-23 20:19:17 UTC