SQLite 授权回调的 IGNORE:列值被隐藏了,为什么带条件的整行也不见了

前天 4阅读

一个查询工具想保留记录编号,但不返回某个字段,于是在SQLite授权回调里对该字段返回SQLITE_IGNORE。直接SELECT时字段变成空值,看起来符合预期;再加一个字段大于零的条件,整条记录却消失了。这个返回值改变的是查询使用的列值,不只是最后显示出来的文字。

本例在Linux、CPython 3.12.14、SQLite 3.53.1实跑。保存为demo.py,运行python3 demo.py,只建立内存表和一条虚构数据。两轮使用独立连接,并设cached_statements=0,避免把语句缓存与本次授权观察混在一起。回调只判断操作码与列名,不在内部执行其他SQL。

SQLite 授权回调的 IGNORE:列值被隐藏了,为什么带条件的整行也不见了

AI模型生成概念插图:一条通路留下空白占位,另一条通路被挡住,用来区分替换列值与拒绝整条语句;不是权限产品截图。

完整程序与本地结果

完整可运行程序

import sqlite3

def scenario(action):
    db = sqlite3.connect(":memory:", cached_statements=0)
    try:
        db.execute("CREATE TABLE item(id INTEGER PRIMARY KEY, cost INTEGER)")
        db.execute("INSERT INTO item VALUES(1, 40)")
        db.commit()

        def authorize(code, table, column, database, origin):
            if code == sqlite3.SQLITE_READ and table == "item" and column == "cost":
                return action
            return sqlite3.SQLITE_OK

        db.set_authorizer(authorize)
        if action == sqlite3.SQLITE_IGNORE:
            visible = db.execute("SELECT id, cost FROM item").fetchall()
            filtered = db.execute("SELECT id FROM item WHERE cost > 0").fetchall()
            assert visible == [(1, None)] and filtered == []
            print("IGNORE selected rows:", visible)
            print("IGNORE filtered rows:", filtered)
        else:
            try:
                db.execute("SELECT cost FROM item").fetchall()
            except sqlite3.DatabaseError as exc:
                assert exc.sqlite_errorcode == sqlite3.SQLITE_AUTH
                print("DENY error:", exc.sqlite_errorname)
            else:
                raise AssertionError("reading cost should be denied")
        db.set_authorizer(None)
        actual = db.execute("SELECT cost FROM item").fetchone()[0]
        assert actual == 40
        print("stored value after removal:", actual)
    finally:
        db.close()

scenario(sqlite3.SQLITE_IGNORE)
scenario(sqlite3.SQLITE_DENY)

本次实际输出(以下为结果,不是程序)

IGNORE selected rows: [(1, None)]
IGNORE filtered rows: []
stored value after removal: 40
DENY error: SQLITE_AUTH
stored value after removal: 40

IGNORE让查询中的读取变成NULL

第一条查询得到[(1, None)],编号保留,cost位置对应Python的None。对于SQLITE_READ操作,返回SQLITE_IGNORE会让准备好的语句使用NULL代替这次列读取。底层表中的40没有被改成NULL,后面的恢复读取会直接验证这一点。

第二条查询的cost > 0也使用了被替换后的NULL,因此条件无法满足WHERE筛选,结果是空列表。回调不是只在最后序列化结果时遮住一格,它参与的是语句准备过程。过滤、表达式与统计都可能因此得到不同结果,不能承诺“除了显示值,查询含义完全一样”。

DENY明确拒绝这条语句

另一轮改成SQLITE_DENY,读取cost直接报SQLITE_AUTH。它没有返回一个空列表作为成功结果,也没有把字段留成None,而是在授权检查处拒绝语句。应用需要按产品要求选择明确报错还是允许替代值,不能把两个常量当作程度不同的同一种隐藏。

两轮移除回调后都再次读取到40,证明演示没有修改存储内容。连接只能同时安装一个授权回调,后装会替换先装,连接复用时要安排好安装与恢复责任。本例关闭每轮独立连接,避免把教学策略留给下一项任务。

回调收到操作信息,不是逐行数据

授权回调主要在SQL编译准备阶段运行,不会逐条拿到记录值来做行级判断。参数含义随操作码变化,部分参数可能为None,因此先检查SQLITE_READ,再解释表名和列名。遇到其他操作,本例直接允许,只为缩小实验范围。

这份回调不是完整的任意SQL安全方案。它没有限制所有写操作、函数或资源消耗,也不能把IGNORE概括为所有操作都不执行;例如其他操作码有各自规则。实际接入前,应按允许的语句类别逐项设计策略,并测试返回列、WHERE条件、聚合与错误路径,确认调用者能够理解这些结果变化。

参考资料

资料核验日期:2026年10月2日。以上输出对应文中固定输入和明确运行版本,本地执行退出码为0。

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。