查看源代码 qlc (stdlib v6.2)
此模块为 Mnesia、ETS、Dets 和其他提供对象迭代器风格遍历的数据结构提供查询接口。
概述
此模块为QLC 表提供查询接口。典型的 QLC 表是 Mnesia、ETS 和 Dets 表。还为用户定义的表提供支持,请参阅 实现 QLC 表 部分。 查询使用查询列表推导式 (QLC) 表示。查询的答案由 QLC 表中满足查询 QLC 约束的数据确定。 QLC 类似于 Erlang 参考手册和编程示例中描述的普通列表推导式,不同之处在于模式中引入的变量不能在列表表达式中使用。 在没有优化和诸如 cache
和 unique
等选项的情况下(请参阅常用选项部分),每个不包含 QLC 表的 QLC 都与相同的普通列表推导式评估为相同的答案列表。
普通列表推导式评估为列表,而调用q/1,2
则返回查询句柄。要获取查询的所有答案,必须使用查询句柄作为第一个参数调用eval/1,2
。查询句柄本质上是在调用 q/1,2
的模块中创建的函数对象 (fun)。由于 fun 引用模块代码,如果模块代码要被替换,请注意不要保留查询句柄太久。代码替换在 Erlang 参考手册中的编译和代码加载部分中描述。还可以通过使用查询游标以块的形式遍历答案列表。查询游标是通过使用查询句柄作为第一个参数调用 cursor/1,2
创建的。查询游标本质上是 Erlang 进程。一次一个答案从查询游标进程发送到创建游标的进程。
语法
从语法上讲,QLC 与普通列表推导式具有相同的部分
[Expression || Qualifier1, Qualifier2, ...]
Expression
(模板)是任何 Erlang 表达式。限定符是过滤器或生成器。过滤器是返回 boolean/0
的 Erlang 表达式。 生成器的形式为 Pattern <- ListExpression
,其中 ListExpression
是评估为查询句柄或列表的表达式。查询句柄从 append/1,2
、keysort/2,3
、q/1,2
、sort/1,2
、string_to_handle/1,2,3
和 table/2
返回。
评估
查询句柄按以下顺序评估
- 检查选项和收集有关表的信息。因此,限定符在优化阶段被修改。
- 评估所有列表表达式。如果创建了游标,则在游标进程中进行评估。对于作为 QLC 的列表表达式,也会评估 QLC 的生成器的列表表达式。如果列表表达式有副作用,请小心,因为列表表达式以未指定的顺序进行评估。
- 通过从左到右评估限定符来查找答案,当某个过滤器返回
false
时回溯,或者当所有过滤器都返回true
时收集模板。
不返回 boolean/0
但失败的过滤器会根据它们的语法以不同的方式处理:如果过滤器是保护式,则返回 false
,否则查询评估失败。 此行为使 qlc
模块可以在不影响查询含义的情况下进行一些优化。例如,当测试表的某个位置和一个或多个常量是否相等时,只有具有相等值的对象才是进一步评估的候选项。 保证其他对象使过滤器返回 false
,但永远不会失败。通常可以通过查找表的某些键值或使用匹配规范遍历表来找到(小的)候选对象集。必须将保护式过滤器立即放在表生成器之后,否则候选对象不会被限制为一个小集合。原因是可能导致查询评估失败的对象不能通过查找键或运行匹配规范来排除。
连接
qlc
模块支持两个查询句柄的快速连接。如果测试一个查询句柄的某个位置 P1
和另一个查询句柄的某个位置 P2
是否相等,则可以进行快速连接。 提供两种快速连接方法
- 查找连接遍历一个查询句柄的所有对象,并查找另一个句柄(QLC 表)的对象,使得
P1
和P2
处的值匹配或比较相等。qlc
模块不创建任何索引,而是使用 QLC 表的键位置和索引位置查找值。 - 合并连接如果必要,对每个查询句柄的对象进行排序,并筛选出
P1
和P2
的值不相等的对象。 如果存在许多具有相同P2
值的对象,则使用临时文件来存储等价类。
如果 QLC 以允许进行多次连接的方式组合查询句柄,则 qlc
模块在编译时会发出警告。 也就是说,不提供可以在可能的连接操作之间选择良好顺序的查询规划器。 由用户通过引入查询句柄来对连接进行排序。
连接要表示为保护式过滤器。过滤器必须紧跟在两个连接的生成器之后,可能在仅使用来自两个连接的生成器的变量的保护式过滤器之后。 qlc
模块在确定要考虑的连接时,会检查 =:=/2
、==/2
、is_record/2
、element/2
和逻辑运算符(and/2
、or/2
、andalso/2
、orelse/2
、xor/2
)的操作数。
常用选项
cursor/2
、eval/2
、fold/4
和 info/2
接受以下选项
{cache_all, Cache}
,其中Cache
等于ets
或list
,将{cache, Cache}
选项添加到查询的每个列表表达式,但表和列表除外。 默认为{cache_all, no}
。 选项cache_all
等效于{cache_all, ets}
。{max_list_size, MaxListSize}
,其中MaxListSize
是外部格式的术语大小(以字节为单位)。 如果收集的对象的累积大小超过MaxListSize
,则将这些对象写入临时文件。 此选项由选项{cache, list}
和合并连接方法使用。 默认为 512*1024 字节。{tmpdir_usage, TmpFileUsage}
确定当qlc
即将在选项tmpdir
设置的目录上创建临时文件时所采取的操作。 如果该值为not_allowed
,则返回错误元组,否则根据需要创建临时文件。 默认为allowed
,这意味着不采取进一步操作。 值info_msg
、warning_msg
和error_msg
表示调用模块error_logger
中具有相应名称的函数来打印一些信息(当前为堆栈跟踪)。{tmpdir, TempDirectory}
设置合并连接用于临时文件的目录以及选项{cache, list}
使用的目录。 该选项还覆盖keysort/3
和sort/2
的选项tmpdir
。 默认为""
,这意味着使用file:get_cwd()
返回的目录。{unique_all, true}
将{unique, true}
选项添加到查询的每个列表表达式。 默认为{unique_all, false}
。 选项unique_all
等效于{unique_all, true}
。
入门
如前所述,查询以 Erlang 参考手册中表达式部分中描述的列表推导式语法表示。 以下内容假定对列表推导式有一些了解。 可以从编程示例中的列表推导式部分中的示例开始。 请注意,列表推导式没有为该语言添加任何计算能力;使用列表推导式可以完成的任何事情也可以在没有它们的情况下完成。 但是,它们添加了用于表达简单搜索问题的语法,一旦您习惯了它,它将变得简洁明了。
许多列表推导式表达式可以由 qlc
模块评估。例外情况是表达式,即模式(或过滤器)中引入的变量在列表推导式中稍后的某个生成器中使用。 例如,考虑一个 lists:append(L)
的实现:[X ||Y <- L, X <- Y]
。Y
在第一个生成器中引入,并在第二个生成器中使用。当可以选择使用哪个时,通常首选普通列表推导式。一个区别是 eval/1,2
在最终反转的列表中收集答案,而列表推导式在最终展开的堆栈中收集答案。
qlc
模块为列表推导式主要增加的功能是从 QLC 表中以小块读取数据。QLC 表是通过调用 qlc:table/2
创建的。通常,qlc:table/2
不是直接从查询中调用的,而是通过某些数据结构的接口函数调用的。Erlang/OTP 包括一些此类函数的示例:mnesia:table/1,2
、ets:table/1,2
和 dets:table/1,2
。对于给定的数据结构,许多函数可以创建 QLC 表,但这些函数的共同点是它们返回由 qlc:table/2
创建的查询句柄。使用 Erlang/OTP 提供的 QLC 表通常可能就足够了,但对于更高级的用户,实现 QLC 表 部分描述了调用 qlc:table/2
的函数的实现。
除了 qlc:table/2
之外,其他函数也会返回查询句柄。它们比表的使用频率更低,但有时很有用。qlc:append/1,2
逐个遍历多个表或列表中的对象。例如,如果要遍历查询 QH
的所有答案,然后以项 {finished}
结束,则可以通过调用 qlc:append(QH, [{finished}])
来完成。 append/2
首先返回 QH
的所有对象,然后返回 {finished}
。如果 QH
的答案中存在元组 {finished}
,则 append/2
会返回两次。
作为另一个示例,考虑连接两个查询 QH1
和 QH2
的答案,同时删除所有重复项。这可以通过使用选项 unique
来完成。
qlc:q([X || X <- qlc:append(QH1, QH2)], {unique, true})
代价是相当大的:每个返回的答案都存储在 ETS 表中。在返回答案之前,会在 ETS 表中查找该答案,以检查它是否已被返回。如果没有 unique
选项,则会返回 QH1
的所有答案,然后返回 QH2
的所有答案。unique
选项会保留剩余答案之间的顺序。
如果答案的顺序不重要,则可以使用 unique
选项的替代方法,即对答案进行唯一排序。
qlc:sort(qlc:q([X || X <- qlc:append(QH1, QH2)], {unique, true})).
此查询也会删除重复项,但答案是排序的。如果有许多答案,则会使用临时文件。请注意,要获得第一个唯一答案,必须找到并排序所有答案。两种替代方法都通过比较答案来查找重复项,也就是说,如果按顺序找到答案 A1
和 A2
,则如果 A1 == A2
,则会删除 A2
。
要仅返回少量答案,可以使用游标。以下代码使用 ETS 表来存储唯一答案,最多返回五个答案。
C = qlc:cursor(qlc:q([X || X <- qlc:append(QH1, QH2)],{unique,true})),
R = qlc:next_answers(C, 5),
ok = qlc:delete_cursor(C),
R.
QLC 方便地用于声明来自两个或多个表的数据约束。以下示例在位置 2 对两个查询句柄执行自然连接。
qlc:q([{X1,X2,X3,Y1} ||
{X1,X2,X3} <- QH1,
{Y1,Y2} <- QH2,
X2 =:= Y2])
qlc
模块根据查询句柄 QH1
和 QH2
以不同的方式评估此操作。例如,如果 X2
与 QLC 表的键匹配,则查找连接方法会在表中查找键值时遍历 QH2
的对象。但是,如果 X2
或 Y2
都未与 QLC 表的键或索引位置匹配,则合并连接方法会确保 QH1
和 QH2
都按位置 2 排序,然后通过逐个遍历对象来执行连接。
可以使用选项 join
强制 qlc
模块使用特定的连接方法。在本节的其余部分中,假设选择了称为“嵌套循环”的过度缓慢的连接方法。
qlc:q([{X1,X2,X3,Y1} ||
{X1,X2,X3} <- QH1,
{Y1,Y2} <- QH2,
X2 =:= Y2],
{join, nested_loop})
在这种情况下,过滤器会一次应用于 QH1
和 QH2
的每对可能的答案。如果 QH1
有 M 个答案,QH2
有 N 个答案,则过滤器会运行 M*N 次。
如果 QH2
是对 gb_trees
的函数的调用,如 实现 QLC 表 部分中所定义,则 gb_table:table/1
(gb 树的迭代器)会针对 QH1
的每个答案启动。然后,gb 树的对象会逐个返回。这可能是遍历表的最有效方法,因为它只需要最少的计算能力即可获取下一个对象。但是,如果 QH2
不是表,而是一个更复杂的 QLC,则使用一些 RAM 内存来收集缓存中的答案可能更有效,尤其是当只有少量答案时。然后,必须假设评估 QH2
没有副作用,这样如果 QH2
只评估一次,则查询的含义不会改变。缓存答案的一种方法是首先评估 QH2
,然后在查询中将答案列表替换为 QH2
。另一种方法是使用选项 cache
。它的表达方式如下
QH2' = qlc:q([X || X <- QH2], {cache, ets})
或者只有
QH2' = qlc:q([X || X <- QH2], cache)
选项 cache
的效果是,当生成器 QH2'
第一次运行时,每个答案都会存储在 ETS 表中。当尝试 QH1
的下一个答案时,QH2'
的答案会从 ETS 表复制,这非常快。至于选项 unique
,代价可能是大量的 RAM 内存。
选项 {cache, list}
提供了将答案存储在进程堆上的列表中的可能性。这有可能比 ETS 表更快,因为无需从表中复制答案。但是,由于进程堆的垃圾回收次数更多,并且由于较大的堆而增加了 RAM 内存消耗,因此它通常会导致评估速度变慢。缓存列表的另一个缺点是,如果列表大小超过限制,则会使用临时文件。从文件中读取答案比从 ETS 表中复制答案慢得多。但是,如果可用的 RAM 内存很少,则将 限制 设置为较低的值是一种替代方法。
在评估查询时,可以将选项 cache_all
设置为 ets
或 list
。它将 cache
或 {cache, list}
选项添加到查询的所有级别上的每个列表表达式,但 QLC 表和列表除外。这可用于测试缓存是否可以提高效率。如果答案是肯定的,则需要进一步测试以查明要缓存的生成器。
实现 QLC 表
作为如何使用函数 table/2
的示例,给出了 gb_trees
模块的 QLC 表的实现。
-module(gb_table).
-export([table/1]).
table(T) ->
TF = fun() -> qlc_next(gb_trees:next(gb_trees:iterator(T))) end,
InfoFun = fun(num_of_objects) -> gb_trees:size(T);
(keypos) -> 1;
(is_sorted_key) -> true;
(is_unique_objects) -> true;
(_) -> undefined
end,
LookupFun =
fun(1, Ks) ->
lists:flatmap(fun(K) ->
case gb_trees:lookup(K, T) of
{value, V} -> [{K,V}];
none -> []
end
end, Ks)
end,
FormatFun =
fun({all, NElements, ElementFun}) ->
ValsS = io_lib:format("gb_trees:from_orddict(~w)",
[gb_nodes(T, NElements, ElementFun)]),
io_lib:format("gb_table:table(~s)", [ValsS]);
({lookup, 1, KeyValues, _NElements, ElementFun}) ->
ValsS = io_lib:format("gb_trees:from_orddict(~w)",
[gb_nodes(T, infinity, ElementFun)]),
io_lib:format("lists:flatmap(fun(K) -> "
"case gb_trees:lookup(K, ~s) of "
"{value, V} -> [{K,V}];none -> [] end "
"end, ~w)",
[ValsS, [ElementFun(KV) || KV <- KeyValues]])
end,
qlc:table(TF, [{info_fun, InfoFun}, {format_fun, FormatFun},
{lookup_fun, LookupFun},{key_equality,'=='}]).
qlc_next({X, V, S}) ->
[{X,V} | fun() -> qlc_next(gb_trees:next(S)) end];
qlc_next(none) ->
[].
gb_nodes(T, infinity, ElementFun) ->
gb_nodes(T, -1, ElementFun);
gb_nodes(T, NElements, ElementFun) ->
gb_iter(gb_trees:iterator(T), NElements, ElementFun).
gb_iter(_I, 0, _EFun) ->
'...';
gb_iter(I0, N, EFun) ->
case gb_trees:next(I0) of
{X, V, I} ->
[EFun({X,V}) | gb_iter(I, N-1, EFun)];
none ->
[]
end.
TF
是遍历函数。qlc
模块要求必须有一种遍历数据结构所有对象的方法。gb_trees
具有适用于该目的的迭代器函数。请注意,对于返回的每个对象,都会创建一个新的 fun。只要列表未以 []
终止,就假定列表的尾部是一个无参数函数,并且调用该函数会返回更多对象(和函数)。
查找函数是可选的。假设查找函数查找值的速度总是比遍历表的速度快得多。第一个参数是键的位置。由于 qlc_next/1
以 {Key, Value}
对的形式返回对象,因此位置为 1。请注意,查找函数要返回 {Key, Value}
对,就像遍历函数一样。
格式函数也是可选的。它由 info/1,2
调用,以在运行时提供有关如何评估查询的反馈。尽量提供尽可能好的反馈,而不要显示太多细节。在示例中,最多显示表的七个对象。格式函数处理两种情况:all
表示遍历表的所有对象;{lookup, 1, KeyValues}
表示使用查找函数来查找键值。
遍历整个表还是只查找一些键取决于查询的表达方式。如果查询的形式为
qlc:q([T || P <- LE, F])
且 P
是一个元组,则 qlc
模块会在编译时分析 P
和 F
,以查找元组 P
中测试是否与常量相等的那些位置。如果在运行时发现此类位置是键位置,则可以使用查找函数,否则必须遍历表的所有对象。信息函数 InfoFun
返回键位置。也可能存在索引位置,它们也由信息函数返回。索引是一个额外的表,可加快在某些位置的查找速度。Mnesia 会根据请求维护索引,并引入所谓的二级键。qlc
模块倾向于在使用二级键之前先使用键查找对象,而与要查找的常量数量无关。
键相等性
Erlang/OTP 有两个运算符用于测试项相等性:==/2
和 =:=/2
。区别在于可以用浮点数表示的整数。例如,2 == 2.0
的计算结果为 true
,而 2 =:= 2.0
的计算结果为 false
。通常,这是一个小问题,但 qlc
模块不能忽略此差异,这会影响用户在 QLC 中对运算符的选择。
如果 qlc
模块在编译时可以确定某些常量不包含整数,则使用 ==/2
或 =:=/2
中的哪一个都无关紧要
1> E1 = ets:new(t, [set]), % uses =:=/2 for key equality
Q1 = qlc:q([K ||
{K} <- ets:table(E1),
K == 2.71 orelse K == a]),
io:format("~s~n", [qlc:info(Q1)]).
ets:match_spec_run(
lists:flatmap(fun(V) ->
ets:lookup(#Ref<0.3098908599.2283929601.256025>,
V)
end,
[a, 2.71]),
ets:match_spec_compile([{{'$1'}, [], ['$1']}]))
在示例中,运算符 ==/2
的处理方式与 =:=/2
的处理方式完全相同。但是,如果在编译时无法确定某些常量不包含整数,并且该表在比较键的相等性时使用 =:=/2
(请参阅选项 key_equality),则 qlc
模块不会尝试查找常量。原因是在一般情况下,可以与此类常量比较相等的键值的数量没有上限;必须查找整数和浮点数的每种组合。
2> E2 = ets:new(t, [set]),
true = ets:insert(E2, [{{2,2},a},{{2,2.0},b},{{2.0,2},c}]),
F2 = fun(I) ->
qlc:q([V || {K,V} <- ets:table(E2), K == I])
end,
Q2 = F2({2,2}),
io:format("~s~n", [qlc:info(Q2)]).
ets:table(#Ref<0.3098908599.2283929601.256125>,
[{traverse,
{select,
[{{'$1', '$2'}, [{'==', '$1', {const, {2, 2}}}], ['$2']}]}}])
3> lists:sort(qlc:e(Q2)).
[a,b,c]
仅查找 {2,2}
不会返回 b
和 c
。
如果表在比较键的相等性时使用 ==/2
,则无论 QLC 中使用哪个运算符,qlc
模块都会查找常量。但是,最好使用 ==/2
。
4> E3 = ets:new(t, [ordered_set]), % uses ==/2 for key equality
true = ets:insert(E3, [{{2,2.0},b}]),
F3 = fun(I) ->
qlc:q([V || {K,V} <- ets:table(E3), K == I])
end,
Q3 = F3({2,2}),
io:format("~s~n", [qlc:info(Q3)]).
ets:match_spec_run(ets:lookup(#Ref<0.3098908599.2283929601.256211>,
{2, 2}),
ets:match_spec_compile([{{'$1', '$2'}, [], ['$2']}]))
5> qlc:e(Q3).
[b]
查找连接的处理方式与在表中查找常量类似:如果连接运算符是 ==/2
,并且要查找常量的表在测试键的相等性时使用 =:=/2
,则 qlc
模块不会考虑该表的查找连接。
另请参阅
dets
, erl_eval
, erlang
, error_logger
, ets
, file
, file_sorter
, mnesia
, shell
, Erlang 参考手册, 编程示例
摘要
类型
Erlang 表达式的解析树,请参阅 ERTS 用户指南中的 抽象格式 部分。
匹配规范,请参阅 ERTS 用户指南中的 Erlang 中的匹配规范 部分以及 ms_transform
。
一个大于 1 的整数。
一个 查询游标。
一个 查询句柄。
一个字面的 查询列表推导。
有关选项的说明,请参阅 file_sorter
。
函数
返回一个查询句柄。当评估查询句柄 QH
时,将返回 QHL
中第一个查询句柄的所有答案,然后是 QHL
中其余查询句柄的所有答案。
返回一个查询句柄。当评估查询句柄 QH3
时,将返回 QH1
的所有答案,然后是 QH2
的所有答案。
等效于 cursor(QH, [])
。
创建一个查询游标,并使调用进程成为该游标的所有者。
删除一个查询游标。只有游标的所有者才能删除该游标。
等效于 eval(QH, [])
。
等效于 eval(QH, [])
。
在调用进程中评估查询句柄,并将所有答案收集到一个列表中。
对查询句柄的连续答案调用 Function
,并附加一个额外的参数 AccIn
。
返回一个英文描述性字符串,描述 qlc
模块或解析转换的某些函数返回的错误元组。此函数主要由调用解析转换的编译器使用。
等效于 info(QH, [])
。
返回有关查询句柄的信息。这些信息描述了为评估查询而准备的简化和优化结果。此函数可能主要在调试期间有用。
返回一个查询句柄。当评估查询句柄 QH2
时,查询句柄 QH1
的答案将根据选项通过 file_sorter:keysort/4
进行排序。
返回查询游标的剩余答案中的一些或全部。只有 QueryCursor
的所有者才能检索答案。
等效于 q(QLC, [])
。
返回 QLC 的查询句柄。QLC 必须是此函数的第一个参数,否则它将作为普通的列表推导进行评估。还需要将以下行添加到源代码中
等效于 sort(QH, [])
。
返回一个查询句柄。当评估查询句柄 QH2
时,查询句柄 QH1
的答案将根据选项通过 file_sorter:sort/3
进行排序。
返回 QLC 表的查询句柄。在 Erlang/OTP 中,支持 ETS、Dets 和 Mnesia 表,但许多其他数据结构也可以转换为 QLC 表。这是通过让实现数据结构的模块中的函数通过调用 qlc:table/2
创建查询句柄来实现的。
类型
-type abstract_expr() :: erl_parse:abstract_expr().
Erlang 表达式的解析树,请参阅 ERTS 用户指南中的 抽象格式 部分。
-type answer() :: term().
-type answers() :: [answer()].
-type cache() :: ets | list | no.
-type key_pos() :: pos_integer() | [pos_integer()].
-type match_expression() :: ets:match_spec().
匹配规范,请参阅 ERTS 用户指南中的 Erlang 中的匹配规范 部分以及 ms_transform
。
-type max_list_size() :: non_neg_integer().
-type no_files() :: pos_integer().
一个大于 1 的整数。
-type order() :: ascending | descending | order_fun().
-opaque query_cursor()
一个 查询游标。
-opaque query_handle()
一个 查询句柄。
-type query_handle_or_list() :: query_handle() | list().
-type query_list_comprehension() :: term().
一个字面的 查询列表推导。
-type sort_option() :: {compressed, boolean()} | {no_files, no_files()} | {order, order()} | {size, pos_integer()} | {tmpdir, tmp_directory()} | {unique, boolean()}.
有关选项的说明,请参阅 file_sorter
。
-type sort_options() :: [sort_option()] | sort_option().
-type spawn_options() :: default | [proc_lib:spawn_option()].
-type tmp_directory() :: [] | file:name().
-type tmp_file_usage() :: allowed | not_allowed | info_msg | warning_msg | error_msg.
函数
-spec append(QHL) -> QH when QHL :: [query_handle_or_list()], QH :: query_handle().
返回一个查询句柄。当评估查询句柄 QH
时,将返回 QHL
中第一个查询句柄的所有答案,然后是 QHL
中其余查询句柄的所有答案。
-spec append(QH1, QH2) -> QH3 when QH1 :: query_handle_or_list(), QH2 :: query_handle_or_list(), QH3 :: query_handle().
返回一个查询句柄。当评估查询句柄 QH3
时,将返回 QH1
的所有答案,然后是 QH2
的所有答案。
-spec cursor(QH) -> Cursor when QH :: query_handle_or_list(), Cursor :: query_cursor().
等效于 cursor(QH, [])
。
-spec cursor(QH, Options) -> Cursor when QH :: query_handle_or_list(), Options :: [Option] | Option, Option :: {cache_all, cache()} | cache_all | {max_list_size, max_list_size()} | {spawn_options, spawn_options()} | {tmpdir_usage, tmp_file_usage()} | {tmpdir, tmp_directory()} | {unique_all, boolean()} | unique_all, Cursor :: query_cursor().
创建一个查询游标,并使调用进程成为该游标的所有者。
游标将用作 next_answers/1,2
和(最终)delete_cursor/1
的参数。调用 erlang:spawn_opt/2
来生成并链接到评估查询句柄的进程。当调用 spawn_opt/2
时,选项 spawn_options
的值将用作最后一个参数。默认为 [link]
。
示例
1> QH = qlc:q([{X,Y} || X <- [a,b], Y <- [1,2]]),
QC = qlc:cursor(QH),
qlc:next_answers(QC, 1).
[{a,1}]
2> qlc:next_answers(QC, 1).
[{a,2}]
3> qlc:next_answers(QC, all_remaining).
[{b,1},{b,2}]
4> qlc:delete_cursor(QC).
ok
-spec delete_cursor(QueryCursor) -> ok when QueryCursor :: query_cursor().
删除一个查询游标。只有游标的所有者才能删除该游标。
-spec e(QH) -> Answers | Error when QH :: query_handle_or_list(), Answers :: answers(), Error :: {error, module(), Reason}, Reason :: file_sorter:reason().
等效于 eval(QH, [])
。
-spec e(QH, Options) -> Answers | Error when QH :: query_handle_or_list(), Options :: [Option] | Option, Option :: {cache_all, cache()} | cache_all | {max_list_size, max_list_size()} | {tmpdir_usage, tmp_file_usage()} | {tmpdir, tmp_directory()} | {unique_all, boolean()} | unique_all, Answers :: answers(), Error :: {error, module(), Reason}, Reason :: file_sorter:reason().
等效于 eval(QH, Options)
。
-spec eval(QH) -> Answers | Error when QH :: query_handle_or_list(), Answers :: answers(), Error :: {error, module(), Reason}, Reason :: file_sorter:reason().
等效于 eval(QH, [])
。
-spec eval(QH, Options) -> Answers | Error when QH :: query_handle_or_list(), Answers :: answers(), Options :: [Option] | Option, Option :: {cache_all, cache()} | cache_all | {max_list_size, max_list_size()} | {tmpdir_usage, tmp_file_usage()} | {tmpdir, tmp_directory()} | {unique_all, boolean()} | unique_all, Error :: {error, module(), Reason}, Reason :: file_sorter:reason().
在调用进程中评估查询句柄,并将所有答案收集到一个列表中。
示例
1> QH = qlc:q([{X,Y} || X <- [a,b], Y <- [1,2]]),
qlc:eval(QH).
[{a,1},{a,2},{b,1},{b,2}]
-spec fold(Function, Acc0, QH) -> Acc1 | Error when QH :: query_handle_or_list(), Function :: fun((answer(), AccIn) -> AccOut), Acc0 :: term(), Acc1 :: term(), AccIn :: term(), AccOut :: term(), Error :: {error, module(), Reason}, Reason :: file_sorter:reason().
-spec fold(Function, Acc0, QH, Options) -> Acc1 | Error when QH :: query_handle_or_list(), Function :: fun((answer(), AccIn) -> AccOut), Acc0 :: term(), Acc1 :: term(), AccIn :: term(), AccOut :: term(), Options :: [Option] | Option, Option :: {cache_all, cache()} | cache_all | {max_list_size, max_list_size()} | {tmpdir_usage, tmp_file_usage()} | {tmpdir, tmp_directory()} | {unique_all, boolean()} | unique_all, Error :: {error, module(), Reason}, Reason :: file_sorter:reason().
对查询句柄的连续答案调用 Function
,并附加一个额外的参数 AccIn
。
查询句柄和函数在调用进程中评估。Function
必须返回一个新的累加器,该累加器将传递给下一次调用。如果查询句柄没有答案,则返回 Acc0
。
示例
1> QH = [1,2,3,4,5,6],
qlc:fold(fun(X, Sum) -> X + Sum end, 0, QH).
21
-spec format_error(Error) -> Chars when Error :: {error, module(), term()}, Chars :: io_lib:chars().
返回一个英文描述性字符串,描述 qlc
模块或解析转换的某些函数返回的错误元组。此函数主要由调用解析转换的编译器使用。
-spec info(QH) -> Info when QH :: query_handle_or_list(), Info :: abstract_expr() | string().
等效于 info(QH, [])
。
-spec info(QH, Options) -> Info when QH :: query_handle_or_list(), Options :: [Option] | Option, Option :: EvalOption | ReturnOption, EvalOption :: {cache_all, cache()} | cache_all | {max_list_size, max_list_size()} | {tmpdir_usage, tmp_file_usage()} | {tmpdir, tmp_directory()} | {unique_all, boolean()} | unique_all, ReturnOption :: {depth, Depth} | {flat, boolean()} | {format, Format} | {n_elements, NElements}, Depth :: infinity | non_neg_integer(), Format :: abstract_code | string, NElements :: infinity | pos_integer(), Info :: abstract_expr() | string().
返回有关查询句柄的信息。这些信息描述了为评估查询而准备的简化和优化结果。此函数可能主要在调试期间有用。
信息的形式是 Erlang 表达式,其中最有可能出现 QLC。根据所提到的 QLC 表的格式化函数,不能确定该信息是否绝对准确。
选项
- 默认情况下,返回一个块中的 QLC 序列,但如果指定选项
{flat, false}
,则返回单个 QLC。 - 默认情况下返回字符串,但如果指定了选项
{format, abstract_code}
,则会返回抽象代码。在抽象代码中,端口标识符、引用和进程 ID 由字符串表示。 - 默认情况下返回列表中的所有元素,但如果指定了选项
{n_elements, NElements}
,则只返回有限数量的元素。 - 默认情况下显示对象和匹配规范的所有部分,但如果指定了选项
{depth, Depth}
,则低于特定深度的项的部分将被替换为'...'
。
示例
在以下示例中,插入了两个简单的 QLC,仅用于保存选项 {unique, true}
1> QH = qlc:q([{X,Y} || X <- [x,y], Y <- [a,b]]),
io:format("~s~n", [qlc:info(QH, unique_all)]).
begin
V1 =
qlc:q([
SQV ||
SQV <- [x, y]
],
[{unique, true}]),
V2 =
qlc:q([
SQV ||
SQV <- [a, b]
],
[{unique, true}]),
qlc:q([
{X,Y} ||
X <- V1,
Y <- V2
],
[{unique, true}])
end
在以下示例中,已插入 QLC V2
以显示连接的生成器和选择的连接方法。查找连接使用以下约定:第一个生成器 (G2
) 是被遍历的生成器,第二个生成器 (G1
) 是查找常量的表。
1> E1 = ets:new(e1, []),
E2 = ets:new(e2, []),
true = ets:insert(E1, [{1,a},{2,b}]),
true = ets:insert(E2, [{a,1},{b,2}]),
Q = qlc:q([{X,Z,W} ||
{X, Z} <- ets:table(E1),
{W, Y} <- ets:table(E2),
X =:= Y]),
io:format("~s~n", [qlc:info(Q)]).
begin
V1 =
qlc:q([
P0 ||
P0 = {W, Y} <-
ets:table(#Ref<0.3098908599.2283929601.256549>)
]),
V2 =
qlc:q([
[G1 | G2] ||
G2 <- V1,
G1 <-
ets:table(#Ref<0.3098908599.2283929601.256548>),
element(2, G1) =:= element(1, G2)
],
[{join, lookup}]),
qlc:q([
{X, Z, W} ||
[{X, Z} | {W, Y}] <- V2
])
end
-spec keysort(KeyPos, QH1) -> QH2 when KeyPos :: key_pos(), QH1 :: query_handle_or_list(), QH2 :: query_handle().
-spec keysort(KeyPos, QH1, SortOptions) -> QH2 when KeyPos :: key_pos(), SortOptions :: sort_options(), QH1 :: query_handle_or_list(), QH2 :: query_handle().
返回一个查询句柄。当评估查询句柄 QH2
时,查询句柄 QH1
的答案将根据选项通过 file_sorter:keysort/4
进行排序。
仅当 QH1
的计算结果不是列表,并且答案的二进制表示形式的大小超过 Size
字节时,排序器才使用临时文件。其中 Size
是选项 size
的值。
-spec next_answers(QueryCursor) -> Answers | Error when QueryCursor :: query_cursor(), Answers :: answers(), Error :: {error, module(), Reason}, Reason :: file_sorter:reason().
等效于 next_answers(C, 10)
。
-spec next_answers(QueryCursor, NumberOfAnswers) -> Answers | Error when QueryCursor :: query_cursor(), Answers :: answers(), NumberOfAnswers :: all_remaining | pos_integer(), Error :: {error, module(), Reason}, Reason :: file_sorter:reason().
返回查询游标的剩余答案中的一些或全部。只有 QueryCursor
的所有者才能检索答案。
参数 NumberOfAnswers
确定返回的最大答案数量。如果返回的答案数量少于请求的数量,则后续调用 next_answers
将返回 []
。
-spec q(QLC) -> QH when QLC :: query_list_comprehension(), QH :: query_handle().
等效于 q(QLC, [])
。
-spec q(QLC, Options) -> QH when QH :: query_handle(), Options :: [Option] | Option, Option :: {max_lookup, MaxLookup} | {cache, cache()} | cache | {join, Join} | {lookup, Lookup} | {unique, boolean()} | unique, MaxLookup :: non_neg_integer() | infinity, Join :: any | lookup | merge | nested_loop, Lookup :: boolean() | any, QLC :: query_list_comprehension().
返回 QLC 的查询句柄。QLC 必须是此函数的第一个参数,否则它将作为普通的列表推导进行评估。还需要将以下行添加到源代码中
-include_lib("stdlib/include/qlc.hrl").
这会导致解析转换将一个 fun 替换为 QLC。当计算查询句柄时,会调用 (编译的) fun。
当从 Erlang shell 调用 qlc:q/1,2
时,会自动调用解析转换。发生这种情况时,替换 QLC 的 fun 不会被编译,而是由 erl_eval
计算。当表达式由 file:eval/1,2
或在调试器中计算时,情况也是如此。
明确地说,这不起作用
...
A = [X || {X} <- [{1},{2}]],
QH = qlc:q(A),
...
变量 A
绑定到列表推导式 ([1,2]
) 的计算值。编译器会报告错误消息(“参数不是查询列表推导式”);shell 进程因 badarg
原因停止。
选项
选项
{cache, ets}
可用于缓存 QLC 的答案。答案存储在每个缓存的 QLC 的一个 ETS 表中。当再次计算缓存的 QLC 时,会从表中获取答案,而无需进行任何进一步的计算。因此,当找到缓存的 QLC 的所有答案时,可以清空用于缓存 QLC 限定符答案的 ETS 表。选项cache
等效于{cache, ets}
。选项
{cache, list}
可用于缓存 QLC 的答案,类似于{cache, ets}
。不同之处在于,答案保存在列表(在进程堆上)中。如果答案占用的 RAM 内存超过一定量,则会使用临时文件存储答案。选项max_list_size
以字节为单位设置限制,临时文件放置在由选项tmpdir
设置的目录中。如果已知 QLC 最多只计算一次,则选项
cache
无效。对于最顶层的 QLC 以及限定符列表中的第一个生成器的列表表达式,始终是如此。请注意,在过滤器或回调函数中存在副作用的情况下,QLC 的答案可能会受到选项cache
的影响。选项
{unique, true}
可用于删除 QLC 的重复答案。唯一答案存储在每个 QLC 的一个 ETS 表中。每次知道 QLC 没有更多答案时,都会清空该表。选项unique
等效于{unique, true}
。如果选项unique
与选项{cache, ets}
结合使用,则会使用两个 ETS 表,但完整答案仅存储在一个表中。如果选项unique
与选项{cache, list}
结合使用,则使用keysort/3
对答案进行两次排序;一次用于删除重复项,一次用于恢复顺序。
选项 cache
和 unique
不仅适用于 QLC 本身,还适用于查找常量、运行匹配规范和连接句柄的结果。
示例
在以下示例中,为每个 A
值遍历合并连接的缓存结果。请注意,如果没有选项 cache
,则会执行三次连接,每个 A
值一次。
1> Q = qlc:q([{A,X,Z,W} ||
A <- [a,b,c],
{X,Z} <- [{a,1},{b,4},{c,6}],
{W,Y} <- [{2,a},{3,b},{4,c}],
X =:= Y],
{cache, list}),
io:format("~s~n", [qlc:info(Q)]).
begin
V1 =
qlc:q([
P0 ||
P0 = {X, Z} <-
qlc:keysort(1, [{a, 1}, {b, 4}, {c, 6}], [])
]),
V2 =
qlc:q([
P0 ||
P0 = {W, Y} <-
qlc:keysort(2, [{2, a}, {3, b}, {4, c}], [])
]),
V3 =
qlc:q([
[G1 | G2] ||
G1 <- V1,
G2 <- V2,
element(1, G1) == element(2, G2)
],
[{join, merge}, {cache, list}]),
qlc:q([
{A, X, Z, W} ||
A <- [a, b, c],
[{X, Z} | {W, Y}] <- V3,
X =:= Y
])
end
sort/1,2
和 keysort/2,3
也可用于缓存答案和删除重复项。当排序的答案缓存在列表中时,可能会存储在临时文件中,并且不使用 ETS 表。
有时(请参阅 table/2
),可以通过查找键值来完成表的遍历,这被认为是快速的。在某些(罕见的)情况下,可能有太多键值需要查找。 选项 {max_lookup, MaxLookup}
可用于限制查找次数:如果需要执行的查找次数超过 MaxLookup
,则不会执行查找,而是遍历表。默认为 infinity
,表示对要查找的键数没有限制。
示例
在以下示例中,使用来自 实现 QLC 表 部分的 gb_table
模块,有六个键要查找:{1,a}
、{1,b}
、{1,c}
、{2,a}
、{2,b}
和 {2,c}
。原因是键 {X, Y}
的两个元素是单独比较的。
1> T = gb_trees:empty(),
QH = qlc:q([X || {{X,Y},_} <- gb_table:table(T),
((X == 1) or (X == 2)) andalso
((Y == a) or (Y == b) or (Y == c))]),
io:format("~s~n", [qlc:info(QH)]).
ets:match_spec_run(
lists:flatmap(fun(K) ->
case
gb_trees:lookup(K,
gb_trees:from_orddict([]))
of
{value, V} ->
[{K, V}];
none ->
[]
end
end,
[{1, a},
{1, b},
{1, c},
{2, a},
{2, b},
{2, c}]),
ets:match_spec_compile([{{{'$1', '$2'}, '_'},
[],
['$1']}]))
选项
选项
{lookup, true}
可用于确保qlc
模块在某些 QLC 表中查找常量。如果生成器的列表表达式中有多个 QLC 表,则必须在至少一个表中查找常量。如果没有要查找的常量,则查询的计算会失败。当遍历某些表中的所有对象是不可接受的时,此选项非常有用。将选项lookup
设置为false
可确保不会查找任何常量({max_lookup, 0}
具有相同的效果)。默认为any
,表示在可能的情况下查找常量。选项
{join, Join}
可用于确保使用特定的连接方法{join, lookup}
调用查找连接方法。{join, merge}
调用合并连接方法。{join, nested_loop}
调用匹配来自两个句柄的每对对象的方法。此方法通常非常慢。
如果
qlc
模块无法执行所选的连接方法,则查询的计算会失败。默认为any
,表示如果可能,则使用某种快速连接方法。
-spec sort(QH1) -> QH2 when QH1 :: query_handle_or_list(), QH2 :: query_handle().
等效于 sort(QH, [])
。
-spec sort(QH1, SortOptions) -> QH2 when SortOptions :: sort_options(), QH1 :: query_handle_or_list(), QH2 :: query_handle().
返回一个查询句柄。当评估查询句柄 QH2
时,查询句柄 QH1
的答案将根据选项通过 file_sorter:sort/3
进行排序。
仅当 QH1
的计算结果不是列表,并且答案的二进制表示形式的大小超过 Size
字节时,排序器才使用临时文件。其中 Size
是选项 size
的值。
-spec string_to_handle(QueryString) -> QH | Error when QueryString :: string(), QH :: query_handle(), Error :: {error, module(), Reason}, Reason :: erl_parse:error_info() | erl_scan:error_info().
-spec string_to_handle(QueryString, Options) -> QH | Error when QueryString :: string(), Options :: [Option] | Option, Option :: {max_lookup, MaxLookup} | {cache, cache()} | cache | {join, Join} | {lookup, Lookup} | {unique, boolean()} | unique, MaxLookup :: non_neg_integer() | infinity, Join :: any | lookup | merge | nested_loop, Lookup :: boolean() | any, QH :: query_handle(), Error :: {error, module(), Reason}, Reason :: erl_parse:error_info() | erl_scan:error_info().
等效于 string_to_handle(QueryString, Options, erl_eval:new_bindings())
。
-spec string_to_handle(QueryString, Options, Bindings) -> QH | Error when QueryString :: string(), Options :: [Option] | Option, Option :: {max_lookup, MaxLookup} | {cache, cache()} | cache | {join, Join} | {lookup, Lookup} | {unique, boolean()} | unique, MaxLookup :: non_neg_integer() | infinity, Join :: any | lookup | merge | nested_loop, Lookup :: boolean() | any, Bindings :: erl_eval:binding_struct(), QH :: query_handle(), Error :: {error, module(), Reason}, Reason :: erl_parse:error_info() | erl_scan:error_info().
q/1,2
的字符串版本。当评估查询句柄时,解析转换创建的函数将由 erl_eval
解释。查询字符串必须是以句点结尾的单个 QLC。
示例
1> L = [1,2,3],
Bs = erl_eval:add_binding('L', L, erl_eval:new_bindings()),
QH = qlc:string_to_handle("[X+1 || X <- L].", [], Bs),
qlc:eval(QH).
[2,3,4]
此函数可能主要在从 Erlang 外部调用时有用,例如,从用 C 编写的驱动程序调用。
注意
以这种方式创建的查询句柄的性能可能比直接通过
q/1,2
创建的查询句柄的性能差。
-spec table(TraverseFun, Options) -> QH when TraverseFun :: TraverseFun0 | TraverseFun1, TraverseFun0 :: fun(() -> TraverseResult), TraverseFun1 :: fun((match_expression()) -> TraverseResult), TraverseResult :: Objects | term(), Objects :: [] | [term() | ObjectList], ObjectList :: TraverseFun0 | Objects, Options :: [Option] | Option, Option :: {format_fun, FormatFun} | {info_fun, InfoFun} | {lookup_fun, LookupFun} | {parent_fun, ParentFun} | {post_fun, PostFun} | {pre_fun, PreFun} | {key_equality, KeyComparison}, FormatFun :: undefined | fun((SelectedObjects) -> FormatedTable), SelectedObjects :: all | {all, NElements, DepthFun} | {match_spec, match_expression()} | {lookup, Position, Keys} | {lookup, Position, Keys, NElements, DepthFun}, NElements :: infinity | pos_integer(), DepthFun :: fun((term()) -> term()), FormatedTable :: {Mod, Fun, Args} | abstract_expr() | string(), InfoFun :: undefined | fun((InfoTag) -> InfoValue), InfoTag :: indices | is_unique_objects | keypos | num_of_objects, InfoValue :: undefined | term(), LookupFun :: undefined | fun((Position, Keys) -> LookupResult), LookupResult :: [term()] | term(), ParentFun :: undefined | fun(() -> ParentFunValue), PostFun :: undefined | fun(() -> term()), PreFun :: undefined | fun((PreArgs) -> term()), PreArgs :: [PreArg], PreArg :: {parent_value, ParentFunValue} | {stop_fun, StopFun}, ParentFunValue :: undefined | term(), StopFun :: undefined | fun(() -> term()), KeyComparison :: '=:=' | '==', Position :: pos_integer(), Keys :: [term()], Mod :: atom(), Fun :: atom(), Args :: [term()], QH :: query_handle().
返回 QLC 表的查询句柄。在 Erlang/OTP 中,支持 ETS、Dets 和 Mnesia 表,但许多其他数据结构也可以转换为 QLC 表。这是通过让实现数据结构的模块中的函数通过调用 qlc:table/2
创建查询句柄来实现的。
遍历表的不同方式和表的属性由作为选项提供给 qlc:table/2
的回调函数处理。
回调函数
TraverseFun
用于遍历表。它会返回一个由[]
或用于遍历表的尚未遍历的对象的零元 fun 终止的对象列表。任何其他返回值都会立即作为查询计算的值返回。一元TraverseFun
接受匹配规范作为参数。匹配规范由解析转换通过分析调用qlc:table/2
的生成器的模式以及使用模式中引入的变量的过滤器来创建。如果解析转换找不到与模式和过滤器等效的匹配规范,则会使用返回每个对象的匹配规范调用TraverseFun
。- 可以使用匹配规范优化表遍历的模块应使用一元
TraverseFun
调用qlc:table/2
。一个示例是ets:table/2
。 - 其他模块可以提供零元
TraverseFun
。一个示例是gb_table:table/1
,位于 实现 QLC 表 部分。
- 可以使用匹配规范优化表遍历的模块应使用一元
一元回调函数
PreFun
在第一次读取表之前调用一次。如果调用失败,则查询计算失败。参数
PreArgs
是标记值的列表。有两个标记parent_value
和stop_fun
,供 Mnesia 用于管理事务。parent_value
的值是由ParentFun
返回的值,如果没有ParentFun
,则为undefined
。ParentFun
在调用eval/1,2
、fold/3,4
或cursor/1,2
的进程上下文中,在调用PreFun
之前调用一次。stop_fun
的值是一个零元 fun,如果从父进程调用,则会删除光标,如果没有光标,则为undefined
。
零元回调函数
PostFun
在上次读取表后调用一次。捕获的返回值将被忽略。如果已为表调用了PreFun
,则保证会为该表调用PostFun
,即使查询的计算因某种原因失败也是如此。不同表的 pre (post) 函数以未指定的顺序计算。
除了读取之外的其他表访问,例如调用
InfoFun
,假定在任何时候都可以。二元回调函数
LookupFun
用于在表中查找对象。第一个参数Position
是键位置或索引位置,第二个参数Keys
是唯一值的排序列表。返回值应为所有对象(元组)的列表,以便Position
处的元素是Keys
的成员。任何其他返回值都会立即作为查询计算的值返回。如果解析转换在编译时可以确定过滤器匹配并以仅需要查找Keys
即可找到所有潜在答案的方式比较Position
处的元素,则会调用LookupFun
而不是遍历表。通过调用
InfoFun(keypos)
获取键的位置,并通过调用InfoFun(indices)
获取索引位置。如果键的位置可用于查找,则始终选择键的位置;否则,选择需要最少查找次数的索引位置。如果两个索引位置之间的查找次数相同,则选择InfoFun
返回的列表中首先出现的那个。需要超过 max_lookup 次查找的位置将被忽略。一元回调函数
InfoFun
用于返回关于表的信息。如果某个标签的值未知,则返回undefined
。indices
- 返回索引位置的列表,即一个正整数列表。is_unique_objects
- 如果TraverseFun
返回的对象是唯一的,则返回true
。keypos
- 返回表键的位置,一个正整数。is_sorted_key
- 如果TraverseFun
返回的对象按键排序,则返回true
。num_of_objects
- 返回表中对象的数量,一个非负整数。
一元回调函数
FormatFun
由info/1,2
用于显示创建表查询句柄的调用。默认为undefined
,这意味着info/1,2
会显示对'$MOD':'$FUN'/0
的调用。FormatFun
负责以适当的方式呈现表的选定对象。但是,如果选择字符列表进行呈现,则它必须是可以扫描和解析的 Erlang 表达式(info/1,2
会添加一个尾随点)。调用
FormatFun
时会传递一个参数,该参数基于对 QLC 过滤器进行分析后进行的优化来描述选定的对象,在 QLC 中,会调用qlc:table/2
。该参数可以具有以下值{lookup, Position, Keys, NElements, DepthFun}
. -LookupFun
用于在表中查找对象。{match_spec, MatchExpression}
- 没有找到通过查找键来查找所有可能答案的方法,但是过滤器可以转换为匹配规范。通过调用TraverseFun(MatchExpression)
找到所有答案。{all, NElements, DepthFun}
- 未找到优化方法。如果TraverseFun
是一元的,则使用匹配所有对象的匹配规范。NElements
是info/1,2
选项n_elements
的值。DepthFun
是一个函数,可以用于限制术语的大小;调用DepthFun(Term)
会将Term
中低于info/1,2
选项depth
指定的深度的部分替换为'...'
。如果使用包含
NElements
和DepthFun
的参数调用FormatFun
失败,则会再次使用不包含NElements
和DepthFun
的参数({lookup, Position, Keys}
或all
)调用FormatFun
。
如果表认为两个键在匹配时相等,则选项
key_equality
的值应为'=:='
;如果两个键在比较时相等,则应为'=='
。默认为'=:='
。