LogiQL 入门
0x00 前言
最近看的工具中用到了 Doop,这是一个以 指针分析 算法为中心的 Java 静态分析框架,使用 Datalog 规则形式表达的各种分析的集合。维护两套规则:
- LogicBlox:使用 LogiQL,Datalog 程序的构建块以 谓词 的形式出现,例如 
Person("John")、Parent("John", "Johnny jr"),利用 规则(IDB 逻辑)从已知的事实中推断出新的信息,例如Ancestor(x, y) <- Parent(x, y) - souffle-lang/souffle (Systematic, Ontological, Undiscovered Fact Finding Logic Engine):由 Datalog 启发的一种编程语言,可用于静态分析、网络分析和数据分析。默认使用该引擎。
 
没看到默认用的是开源的 souffle,结果花了一下午入门 LogiQL 还有找 3.10.x 版本的 LogicBlox …
LogicBlox 一直是闭源软件,目前最新版本 4.x.x 都可以下载,Release Archive (LB 3.10.x) 不再提供下载,可以通过 LogicBlox Academic License Request 申请。找了好久没地方下载还要申请,回头仔细看了看 Doop 的说明文档才发现应该去看 souffle 😅😅😅
LogiQL 入门跟着教程 LogiQL in 30 minutes 走就好了,介绍的特别简单,能够让人刚踩到门槛上吧(大概)。
0x01 LogiQL 入门
实验环境
用在线的 LogicBlox REPL (Read-Eval-Print Loop) 进行实验,REPL 是一个基于命令的交互式环境,可以在其中执行 LogiQL 命令并立即检查结果。
术语
工作区(workspace)指代数据库(database),结合了模式(schema)、逻辑(logic)和数据(data)
实验过程
定义一个冰淇淋商店会计系统,记录以下信息:
- 成本(人工,原料):cost(labor, ingredients)
 - 价格:price
 - 销量/周:sales
 - 每个冰淇淋的收入/周:revenue
 - 所有冰淇淋的收入/周:week revenue
 - 每个冰淇淋的收益/周:profit
 - 所有冰淇淋的收益/周:week profit
 
定义功能谓词(functional predicates)
- 谓词类似 SQL 数据库中的表
 - 除了最后一列之外的所有列都形成一个主键,最后一列表示值,因此一个主键最多可以关联一个值。
 
1. cost 谓词
定义名为 cost 的谓词,键为 icecream,值为 c;指向右侧的箭头表示约束(constraint),icecream 的值必须是字符串、c 的值必须是整数
- 约束表明如果箭头左侧为真,则右侧必须成立
 
1  | cost[icecream] = c ->  | 
在 REPL 中执行需要使用相应的命令,addblock 接受一个参数,在引号之间添加的定义的 LogiQL 代码。
1  | addblock 'cost[icecream] = c ->  | 
添加之后使用 list 命令列出谓词
1  | list  | 
2. 事实(facts)
谓词中的数据条目在 LogiQL 中称为事实(facts)。
LogiQL 支持 3 种增量更新操作
- 插入(insert):插入新事实,使用 
+前缀 - 删除(remove):删除事实,使用 
-前缀 - 更新(upsert):添加或更新,使用 
^前缀,只能应用在功能谓词上 
使用 exec 执行一次性的命令,而 addblock 可以理解为“永久”地将谓词添加到工作区。
添加一个成本为 23 的冰淇淋:
1  | exec '+cost["Popsicle Lemon"] = 23.'  | 
print 命令用于打印谓词。
1  | print cost  | 
添加冰淇淋(成本):
1  | exec '+cost["Fruit Sundae"] = 120.  | 
更新冰淇淋的售价:
1  | exec '^cost["Popsicle Lemon"] = 25.'  | 
3. price 谓词、week_sales 谓词
定义谓词 price 表示冰淇淋的售价,定义谓词 week_sales 表示每周的销售数量(可以把 week_sales 看作二维映射)。
1  | addblock 'price[icecream] = p ->  | 
添加冰淇淋售价及其第 1、2 周的销量(往谓词中添加事实):
1  | exec '+price["Popsicle Lemon"] = 50.  | 
打印谓词:
1  | print price  | 
4. 规则(rules)
规则可用于推导新的事实,添加计算收入 revenue 的规则:
1  | addblock 'week_revenue[icecream, week] =  | 
打印规则,该规则会自动进行计算:
1  | print week_revenue  | 
修改一个价格,会自动进行重新计算:
1  | exec '^price["Popsicle Lemon"] = 70.'  | 
添加计算冰淇淋收益 profit 的规则、计算每周收益的规则 week_profit:
1  | addblock 'profit[icecream] =  | 
然后打印规则,会自动进行计算:
1  | print profit  | 
5. 聚合函数(Aggregate)
和 SQL 类似,LogiQL 中也有聚合函数。
定义 agg_profit 规则,使用 total() 聚合函数求和,将 profit 中所有事实的值 p 求和绑定到 value,其中 week 是聚合的周,使用通配符 _ 忽略冰淇淋类型。表示总收益(收入-成本)。
agg_revenue 规则也是类似的定义,只不过把 p 换成了 r,表示总收入。
1  | agg_profit[week] = value <-  | 
使用 addblock 添加规则:
1  | addblock 'agg_profit[week] = value <-  | 
打印规则,得到聚合的结果:
1  | print agg_profit  | 
定义一个没有键的规则计算 price 谓词中的事实(条目)数量,即冰淇淋类型的数量:
1  | addblock 'icecream_count[] = value <-  | 
打印规则,得到冰淇淋类型的数量:
1  | print icecream_count  | 
6. 查询(query)
之前使用 print 查看谓词和规则的值,现在可以用 query 执行更高级的查询。
定义一个谓词 _ 用事实填充,通常使用规则实现。
下面的查询与 print week_sales 拥有等价的结果。这里不使用功能谓词语法 predicate[k1, k2] = v,而是使用 predicate(k1, k2, k3),因为这里对键值不感兴趣,只对事实感兴趣。
1  | query '_(icecream, week, value) <-  | 
检索 week_sales 谓词中使用的所有冰淇淋,尽管 week_sales 谓词多次包含这些值中的每一个,但每个不同值只返回一次(有 1、2 两周的数据,所以冰淇淋重复出现)
- 在 LogiQL 中所有谓词都是集合:每一行只能出现一次
 
1  | query '_(icecream) <- week_sales[icecream, _] = _.'  | 
将冰淇淋和每周的销售额、收入、收益整合在一起,就像 SQL 中的 JOIN 操作:
1  | query '_(icecream, week, sales, revenue, profit) <-  | 
7. 约束(constraint)
降低冰淇淋的售价,再次查询发现冰淇淋的收益变成了负数:
1  | exec '^price["Popsicle Lemon"] = 20.'  | 
定义一个约束:对于 profit 谓词中的每个事实,value 应该大于等于 0。
1  | profit[_] = value -> value >= 0.  | 
使用 addblock 添加约束,因为 profit 谓词中有事实的 value 值小于 0,所以添加约束失败。
1  | addblock 'profit[_] = value -> value >= 0.'  | 
修改冰淇淋售价后再添加约束:
1  | exec '^price["Popsicle Lemon"] = 40.'  | 
再次修改冰淇淋售价,返回约束错误:
1  | exec '^price["Popsicle Lemon"] = 10.'  | 
0x02 结语
有一说一,这个入门完全不足以让人看懂 doop/lb-logic 里面的规则,做都做了就理上来了XD
补充:本地可以使用 PA-Datalog 引擎,是 LogicBlox v3 的改良版本,可用于程序分析。






