Module: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