模組:Luaq/doc
此頁面為 Module:Luaq 的說明文件
LUAQ是Lua Query的縮寫,通過一系列查詢函數讓以Lua表(table)為主的數據源查詢代碼更加簡潔。
使用配置
在模塊代碼中添加如下代碼:
require("Module:Luaq")
之後便可以通過luaq
使用各種查詢函數。
使用
本章節將會介紹LUAQ的基本概念、查詢函數的文檔說明以及使用時必須或可選的注意事項。
若您想要了解查詢函數的實現細節,或者希望修改結構、添加更多查詢函數,請參閱#開發章節。
基本概念
查詢對象
查詢對象是由LUAQ的查詢函數按照某種規範生成的特殊對象,它的本質是Lua表,但由於其內部的結構對於使用者來說是未知的,所以無法通過一般的手段獲取它的內部結構。
實際上,使用者沒有必要嘗試去獲取它的內部結構,若要獲取它包含的內容,請參閱#查詢展開章節。
延遲查詢
延遲查詢是LUAQ的重要特徵之一,它表現為——當查詢對象被生成時,查詢對象並不會立即展開。唯有調用某些查詢函數時,查詢對象才會被計算,並返回查詢結果。
在「查詢對象被生成時」和「調用立即展開查詢對象的函數」之間,改變作為數據源的Lua表內容將對最終查詢結果會造成影響;當查詢對象被展開後,無論數據源發生任何改變,都不會改變查詢結果。
在#查詢函數章節中,會立即展開查詢對象的函數將注有說明。
查詢展開
查詢展開是指將包含了一系列指令的查詢對象轉化為結果的過程。一共有兩種方式進行展開。
- enum:
enum(query)
全局函數,返回展開查詢對象的迭代器。
函數返回三個值:
- 迭代器函數
- 查詢對象本身
0
迭代器用在泛型for循環中。
- luaq.query:
請參閱luaq.query。
查詢函數調用格式
為了使代碼更加直觀簡潔,LUAQ在Lua函數調用語法糖的基礎上支持了更簡潔的寫法。以下兩種寫法是等價的:
- 函數:
luaq.<查询函数名称>(查询对象[, 参数1[, 参数2[..., 参数n]]])
- 語法糖:
查询对象:<查询函数名称>([, 参数1[, 参数2[..., 参数n]]])
當您使用了語法糖調用形式,並且遇到了莫名其妙的「函数“...”的第1个参数类型错误(应为table,实为...)。"
」報錯,不妨檢查一下是否將:
錯寫成了.
。
查詢函數
luaq.query
luaq.query(query)
展開一個查詢對象。
- 這個函數將立即展開查詢對象,並將查詢結果包裝為一個Lua表返回。
- 參數:
-
query
:要展開的查詢對象。
- 返回:
- 一個Lua表,其數值類型的鍵即為查詢結果每一項的位置索引。
luaq.asQuery
luaq.asQuery(t)
將一個Lua表包裝為查詢對象。
- 當參數
t
已經是查詢對象時,將返回其本身。 - 參數:
-
t
:一個Lua表。
- 錯誤:
函数“asQuery”的第1个参数类型错误(应为table,实为...)。"
:當參數t
的值不是一個Lua表的時候拋出此錯誤。
- 示例:
- 代碼:
<strong class="error"><span class="scribunto-error" id="mw-scribunto-error-0">脚本错误:函数“main”不存在。</span></strong>
- 結果:
<strong class="error"><span class="scribunto-error" id="mw-scribunto-error-1">脚本错误:函数“main”不存在。</span></strong>
luaq.select
luaq.select(query, func)
對舊查詢對象的每一個項進行指定的轉換,並將結果形成新的查詢對象。
- 參數:
-
query
:一個查詢對象,其每一個項將要被轉換。func
:對query
的每一個項進行的轉換函數。轉換函數將會接受到兩個參數值:- 第1個參數值:
query
的當前項。 - 第2個參數值:從開始到當前的索引。
- 第1個參數值:
- 返回:
- 一個查詢對象。它由轉換後的項組成。
- 錯誤:
bad argument #2 to 'select' (table expected, got ...)
:當參數func
的值不是一個函數的時候拋出此錯誤。
- 示例1:
在本示例中,轉換函數返回當前項的值的類型。
- 代碼:
<strong class="error"><span class="scribunto-error" id="mw-scribunto-error-2">脚本错误:函数“main”不存在。</span></strong>
- 結果:
<strong class="error"><span class="scribunto-error" id="mw-scribunto-error-3">脚本错误:函数“main”不存在。</span></strong>
- 示例2:
在本示例中,luaq.select函數判斷當前索引的奇偶性。若索引為奇數,則返回key
欄位的值;若索引為偶數,則返回value
欄位的值。
- 代碼:
local t = { 5, 4, 3, str = "string" , 2, 1} local q = luaq.asQuery(t) :select( function(pair, i) if math.fmod(i, 2) == 1 then return pair.key else return pair.value end end ) local r = q:query() for key, value in pairs(t) do print("key = "..key.."\tvalue = "..value) end print() print("查询结果:") for i, v in ipairs(r) do print(v) end
- 結果:
key = 1 value = 5 key = 2 value = 4 key = 3 value = 3 key = 4 value = 2 key = 5 value = 1 key = str value = string 查询结果: 1 4 3 2 5 string
luaq.where
luaq.where(query, func)
對舊查詢對象的每一個項進行指定的篩選,並將符合條件的結果形成新的查詢對象。
- 參數:
-
query
:一個查詢對象,其每一個項將要被篩選。func
:對query
的每一個項進行檢測的篩選函數。檢測函數應返回true
或false
,且將會接受到兩個參數值:- 第1個參數值:
query
的當前項。 - 第2個參數值:從開始到當前的索引。
- 第1個參數值:
- 返回:
- 一個查詢對象。它由篩選後的項組成。
- 錯誤:
bad argument #2 to 'where' (table expected, got ...)
:當參數func
的值不是一個函數的時候拋出此錯誤。
- 示例:
本示例在luaq.select函數的示例1的基礎上進行篩選。luaq.where函數的篩選機制為「項的值是字符串類型」或「項的值不小於3」。
- 代碼:
local t = { 5, 4, 3, str = "string" , 2, 1} local q = luaq.asQuery(t) :select( function(pair) return type(pair.value) end ) :where( function(e) return type(e) == "string" or e >= 3 end ) local r = q:query() for key, value in pairs(t) do print("key = "..key.."\tvalue = "..value) end print() print("查询结果:") for i, v in ipairs(r) do print(v) end
- 結果:
key = 1 value = 5 key = 2 value = 4 key = 3 value = 3 key = 4 value = 2 key = 5 value = 1 key = str value = string 查询结果: 4 3 5 string
luaq.count
luaq.count(query)
獲取查詢結果中項的數量。
- 這個函數將立即展開查詢對象。
- 參數:
-
query
:一個查詢對象。
- 返回:
- 查詢結果中項的數量。
- 示例:
本示例同時演示了LUAQ的延遲查詢。表中含有6對值鍵對,因此第一次luaq.count查詢的結果為6。
刪去表中的第二個元素後,已通過調用luaq.query展開查詢的變量r
,其循環遍歷輸出的內容不變,但第二次luaq.count查詢的結果變為5。
- 代碼:
local t = { 5, 4, 3, str = "string" , 2, 1} local q = luaq.asQuery(t) local r = q:query() for i, pair in ipairs(r) do print(pair.key.."\t"..pair.value) end print("count() = "..q:count()) print() table.remove(t, 2) --删去表中的第二个元素 for i, pair in ipairs(r) do print(pair.key.."\t"..pair.value) end print("count() = "..q:count())
- 結果:
1 5 2 4 3 3 4 2 5 1 str string count() = 6 1 5 2 4 3 3 4 2 5 1 str string count() = 5
luaq.any
luaq.all
luaq.distinct
luaq.groupBy
開發
本章節將會介紹LUAQ的實現細節、開發相關的文檔說明等。
查詢對象保留欄位
查詢對象本質是一個Lua表。為了判斷一個Lua表是否為查詢對象,同時為了實現LUAQ核心功能,該Lua表必須設置元表(metatable)並且遵守以下約束:
__enum
:(必須)一個函數,返回查詢對象的枚舉器。__reset
:(可選)一個函數,用於重置查詢對象的狀態。__index
:(可選)一個函數,用於按數字索引獲取對應的查詢結果的項目。__ipairs
:(可選)一個函數,用於重寫_G.ipairs函數邏輯。__pairs
:(可選)一個函數,用於重寫_G.pairs函數邏輯。
除此之外,各個LUAQ查詢函數生成的查詢對象的元表也有各自的保留欄位,請在開發中避免占用。
各查詢函數的保留欄位將分別說明。
枚舉器
對每一個查詢對象調用__getenumerator
函數即可獲得其枚舉器。
枚舉器是支持LUAQ延遲查詢的基石,通過改變其內部的狀態可以實現向前、向後查詢等操作。
可以通過以下兩種方式獲取枚舉器的對象:
- 函數:
<查询对象>.__getenumerator(<查询对象>)
- 語法糖:
<查询对象>:__getenumerator()
枚舉器欄位
枚舉器是一個Lua表,為了正常工作,它必須具有以下欄位:
current
:這個欄位的類型為函數,用以獲取當前查詢的項。- 調用:
- 函數:
<枚举器对象>.current(self, query)
- 語法糖:
<枚举器对象>:current(query)
- 參數:
self
:即<枚举器对象>
本身,用以支持語法糖調用。query
:包含<枚举器对象>
的查詢對象。- 返回:
- 當前查詢的項。
moveLast
:這個欄位的類型為返回值為true
或false
函數,用以將枚舉器內部的狀態向後回退一次。- 調用:
- 函數:
<枚举器对象>.moveLast(self, query)
- 語法糖:
<枚举器对象>:moveLast(query)
- 參數:
self
:即<枚举器对象>
本身,用以支持語法糖調用。query
:包含<枚举器对象>
的查詢對象。- 返回:
- 一個值,指示回退操作是否成功。
moveNext
:這個欄位的類型為返回值為true
或false
函數,用以將枚舉器內部的狀態向前前進一次。- 調用:
- 函數:
<枚举器对象>.moveNext(self, query)
- 語法糖:
<枚举器对象>:moveNext(query)
- 參數:
self
:即<枚举器对象>
本身,用以支持語法糖調用。query
:包含<枚举器对象>
的查詢對象。- 返回:
- 一個值,指示前進操作是否成功。
saveState
:這個欄位的類型為函數,用以獲取枚舉器內部的當前狀態。- 調用:
- 函數:
<枚举器对象>.saveState(self, query)
- 語法糖:
<枚举器对象>:saveState(query)
resetState
:這個欄位的類型為函數,用以設置枚舉器內部的當前狀態。- 調用:
- 函數:
<枚举器对象>.resetState(self, query, state)
- 語法糖:
<枚举器对象>:resetState(query, state)
枚舉器工作原理
- 獲取查詢對象
query
。 - 通過
query:__getenumerator()
獲取枚舉器enumerator
。除非特殊設計,一般的枚舉器的初始狀態為起點。 - 調用
enumerator:moveNext(query)
將枚舉器內部狀態前進一次,若返回值為true,則繼續下一步。 - 調用
enumerator:current(query)
獲取當前的項current
。 - 對
current
進行後續處理。
- 示例:
local query = luaq.asQuery({ 5, 4, 3, 2, 1 }) local enumerator = query:__getenumerator() while enumerator:moveNext(query) do local current = enumerator:current(query) print(current.key.."\t"..current.value) end
- 結果:
1 5 2 4 3 3 4 2 5 1