SQLite 授权回调的 IGNORE:列值被隐藏了,为什么带条件的整行也不见了
一个查询工具想保留记录编号,但不返回某个字段,于是在SQLite授权回调里对该字段返回SQLITE_IGNORE。直接SELECT时字段变成空值,看起来符合预期;再加一个字段大于零的条件,整条记录却消失了。这个返回值改变的是查询使用的列值,不只是最后显示出来的文字。
本例在Linux、CPython 3.12.14、SQLite 3.53.1实跑。保存为demo.py,运行python3 demo.py,只建立内存表和一条虚构数据。两轮使用独立连接,并设cached_statements=0,避免把语句缓存与本次授权观察混在一起。回调只判断操作码与列名,不在内部执行其他SQL。
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。


