app.database - SQLite 数据库 API
SQLite 数据库操作。
权限:L2 敏感 — 需声明,安装时显示权限警告(
"database")
模块只有一个入口 app.database.open(path),它返回一个数据库句柄,
之后所有操作都是句柄上的方法,用冒号调用:
local db = app.database.open("~/data/my.db")
db:execute("CREATE TABLE IF NOT EXISTS t (id INTEGER PRIMARY KEY)")
local rows = db:query("SELECT * FROM t")
db:close()
app.database.open(path)
打开或创建数据库。
参数:
path(string) - 数据库文件路径(支持~展开)
返回值: dbHandle - 数据库句柄;失败时返回 nil, error
local db, err = app.database.open("~/data/my.db")
if not db then
app.log.error("打开数据库失败: " .. tostring(err))
return
end
数据库放哪。 别放
app.context.pluginDirectory()——那是随包内容, 升级插件时整个目录会被替换,数据库连同数据一起没。放app.context.dataDirectory(),升级不受影响:
local dbPath = app.path.join(app.context.dataDirectory(), "index.db")
句柄方法
db:execute(sql, params?)
执行 SQL 语句(INSERT / UPDATE / DELETE / CREATE 等)。
参数:
sql(string) - SQL 语句params(array, 可选) - 参数数组,对应 SQL 里的?
返回值: boolean;失败时返回 false, error
-- 创建表
db:execute([[
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE,
created_at INTEGER DEFAULT (strftime('%s', 'now'))
)
]])
-- 插入数据。用参数数组,不要自己拼字符串——那是 SQL 注入
db:execute("INSERT INTO users (name, email) VALUES (?, ?)",
{"张三", "zhangsan@example.com"})
db:query(sql, params?)
查询数据。
参数:
sql(string) - SQL 查询语句params(array, 可选) - 参数数组
返回值: array - 结果行数组,每行是列名到值的表;失败时返回 nil, error
local users = db:query("SELECT * FROM users")
if users then
for _, user in ipairs(users) do
app.log.info(user.name .. " - " .. user.email)
end
end
local results = db:query("SELECT * FROM users WHERE id = ?", {1})
db:begin() / db:commit() / db:rollback()
事务控制。三个都返回 boolean,失败时 false, error。
db:begin()
local ok1 = db:execute("INSERT INTO users (name) VALUES (?)", {"用户1"})
local ok2 = db:execute("INSERT INTO users (name) VALUES (?)", {"用户2"})
if ok1 and ok2 then
db:commit()
else
db:rollback()
end
db:changes()
上次操作影响的行数。
返回值: number
db:execute("UPDATE users SET name = ? WHERE id > ?", {"更新", 10})
local affected = db:changes()
db:lastInsertId()
最后插入的行 ID。
返回值: number
db:execute("INSERT INTO users (name) VALUES (?)", {"测试"})
local id = db:lastInsertId()
db:tableExists(tableName)
表是否存在。
返回值: boolean;失败时 false, error
if not db:tableExists("users") then
db:execute("CREATE TABLE users (id INTEGER PRIMARY KEY)")
end
db:tables()
列出所有表名。
返回值: array;失败时 nil, error
for _, name in ipairs(db:tables() or {}) do
app.log.info("表: " .. name)
end
db:columns(tableName)
某张表的列信息。
返回值: array,每项含 name / type / notnull / defaultValue / primaryKey;
失败时 nil, error
for _, col in ipairs(db:columns("users") or {}) do
app.log.info(col.name .. " - " .. col.type)
end
db:close()
关闭连接。
db:close()
示例
文件索引数据库
function MyPlugin:init()
-- 建在数据目录而不是插件目录:插件目录升级时会被整个替换掉
local dbPath = app.path.join(app.context.dataDirectory(), "index.db")
local db, err = app.database.open(dbPath)
if not db then
app.log.error("打开索引库失败: " .. tostring(err))
return
end
self.db = db
self.db:execute([[
CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY,
path TEXT UNIQUE,
name TEXT,
size INTEGER,
modified INTEGER,
hash TEXT,
indexed_at INTEGER
)
]])
self.db:execute("CREATE INDEX IF NOT EXISTS idx_files_name ON files(name)")
end
function MyPlugin:indexFile(path)
self.db:execute([[
INSERT OR REPLACE INTO files (path, name, size, modified, hash, indexed_at)
VALUES (?, ?, ?, ?, ?, ?)
]], {
path,
app.path.basename(path),
app.file.size(path),
app.file.modificationDate(path),
app.crypto.md5File(path),
app.date.now()
})
end
function MyPlugin:searchFiles(query)
return self.db:query(
"SELECT * FROM files WHERE name LIKE ? ORDER BY modified DESC",
{"%" .. query .. "%"}
)
end
function MyPlugin:cleanup()
if self.db then self.db:close() end
end
批量写入放进一个事务
不开事务时 SQLite 给每条 execute 各自提交一次,每次提交都要落盘。几千条起就很明显,包进一个事务只落盘一次:
db:begin()
for _, row in ipairs(rows) do
db:execute("INSERT INTO t (a, b) VALUES (?, ?)", {row.a, row.b})
end
db:commit()