SQLite¶
核心的 SqliteDatabase 类处理 pragmas、用户定义函数、WAL 模式、全文搜索和 JSON。由于全文搜索和 JSON 字段是 SQLite 特有的,这些功能由 playhouse.sqlite_ext 提供。
本页内容
实现¶
SqliteDatabase核心 SQLite 实现。提供
Pragma 支持(包括 WAL 模式)
用户定义函数
ATTACH / DETACH 数据库
全文搜索
JSON
全文搜索和 JSON 实现可在
playhouse.sqlite_ext中找到。CySqliteDatabase(playhouse.cysqlite_ext)继承自
SqliteDatabase,使用 cysqlite 驱动。以上所有功能
表值函数
Commit / Rollback / Update / Progress / Trace 钩子
BLOB I/O
在线备份
可以 构建为加密版本。
APSWDatabase(playhouse.apsw_ext)继承自
SqliteDatabase,使用 apsw 驱动。APSW 是一个精简的 C 语言驱动,暴露了 SQLite 的全部功能。
SqlCipherDatabase(playhouse.sqlcipher_ext)继承自
SqliteDatabase,使用 sqlcipher3 驱动。SQLCipher 通过 256 位 AES 提供透明的全数据库加密,确保磁盘上的数据安全。
SqliteQueueDatabase(playhouse.sqliteq)继承自
SqliteDatabase。提供了一个 SQLite 数据库实现,带有一个长期运行的后台写入线程。所有写入操作都由单个写入连接管理,从而防止超时和数据库锁定问题。当在多线程环境中使用 SQLite 进行频繁写入时,此实现非常有用。
PRAGMA 语句¶
SQLite 允许通过 PRAGMA 语句进行运行时配置(参见 SQLite 文档)。这些语句通常在新数据库连接创建时运行。
指定连接的默认 PRAGMA 语句
db = SqliteDatabase('my_app.db', pragmas={
'journal_mode': 'wal',
'cache_size': 10000, # 10000 pages, or ~40MB
'foreign_keys': 1, # Enforce foreign-key constraints
})
Pragmas 也可以通过 pragma() 方法或 SqliteDatabase 对象上暴露的特殊属性动态配置。
# Set cache size to 64MB for *current connection*.
db.pragma('cache_size', -64000)
# Same as above.
db.cache_size = -64000
# Read the value of several pragmas:
print('cache_size:', db.cache_size)
print('foreign_keys:', db.foreign_keys)
print('journal_mode:', db.journal_mode)
print('page_size:', db.page_size)
# Set foreign_keys pragma on current connection *AND* on all
# connections opened subsequently.
db.pragma('foreign_keys', 1, permanent=True)
注意
通过 pragma() 方法设置的 Pragmas 在新连接打开时不会重新应用。要配置一个 pragma 以便在每次打开新连接时运行,请指定 permanent=True。
db.pragma('foreign_keys', 1, permanent=True)
另请参阅
SQLite PRAGMA 文档:https://sqlite.ac.cn/pragma.html
用户定义函数¶
SQLite 可以通过用户定义的 Python 代码进行扩展。SqliteDatabase 类支持各种用户定义的扩展:
- 函数
用户定义函数接受任意数量的参数并返回单个值。
- 聚合函数
跨多行聚合值并返回单个值。
- 窗口函数
支持对数据滑动窗口进行操作的聚合函数。
- 排序规则
控制值如何排序和比较。
- 表函数
用户定义的表(需要
cysqlite)。- 共享库
从共享库加载扩展。
函数示例¶
db = SqliteDatabase('analytics.db')
from urllib.parse import urlparse
@db.func('hostname')
def hostname(url):
if url is not None:
return urlparse(url).netloc
# Call this function in our code:
# The following finds the most common hostnames of referrers by count:
query = (PageView
.select(fn.hostname(PageView.referrer), fn.COUNT(PageView.id))
.group_by(fn.hostname(PageView.referrer))
.order_by(fn.COUNT(PageView.id).desc()))
聚合函数示例¶
用户定义的聚合函数必须定义两个方法:
step(*values)- 针对正在聚合的每一行调用一次。finalize()- 只调用一次以产生最终的聚合值。
from hashlib import md5
@db.aggregate('md5')
class MD5Checksum(object):
def __init__(self):
self.checksum = md5()
def step(self, value):
self.checksum.update(value.encode('utf-8'))
def finalize(self):
return self.checksum.hexdigest()
# Usage:
# The following computes an aggregate MD5 checksum for files broken
# up into chunks and stored in the database.
query = (FileChunk
.select(FileChunk.filename, fn.MD5(FileChunk.data))
.group_by(FileChunk.filename)
.order_by(FileChunk.filename, FileChunk.sequence))
窗口函数示例¶
用户定义的窗口函数只是带有两个附加方法的聚合函数:
step(*values)- 针对正在聚合的每一行调用一次。inverse(*values)- “反转” `step(*values)` 调用产生的影响。value()- 返回聚合的当前值。finalize()- 返回最终的聚合值。
# Window functions are normal aggregates with two additional methods:
# inverse(value) - Perform the inverse of step(value).
# value() - Report value at current step.
@db.aggregate('mysum')
class MySum(object):
def __init__(self):
self._value = 0
def step(self, value):
self._value += (value or 0)
def inverse(self, value):
self._value -= (value or 0) # Do opposite of "step()".
def value(self):
return self._value
def finalize(self):
return self._value
# e.g., aggregate sum of employee salaries over their department.
query = (Employee
.select(
Employee.department,
Employee.salary,
fn.mysum(Employee.salary).over(
partition_by=[Employee.department]))
.order_by(Employee.id))
排序规则示例¶
排序规则接受两个值,并提供一个值来指示它们的排序方式(例如 `cmp(lhs, rhs)`)。
@db.collation('ireverse')
def collate_reverse(s1, s2):
# Case-insensitive reverse.
s1, s2 = s1.lower(), s2.lower()
return (s1 < s2) - (s1 > s2) # Equivalent to -cmp(s1, s2)
# To use this collation to sort books in reverse order...
Book.select().order_by(collate_reverse.collation(Book.title))
# Or...
Book.select().order_by(Book.title.asc(collation='reverse'))
表函数示例¶
示例用户定义的表值函数(有关 TableFunction 的完整详细信息,请参阅 cysqlite TableFunction 文档)。
from cysqlite import TableFunction
from playhouse.cysqlite_ext import CySqliteDatabase
db = CySqliteDatabase('my_app.db')
@db.table_function('series')
class Series(TableFunction):
columns = ['value']
params = ['start', 'stop', 'step']
def initialize(self, start=0, stop=None, step=1):
"""
Table-functions declare an initialize() method, which is
called with whatever arguments the user has called the
function with.
"""
self.start = self.current = start
self.stop = stop or float('Inf')
self.step = step
def iterate(self, idx):
"""
Iterate is called repeatedly by the SQLite database engine
until the required number of rows has been read **or** the
function raises a `StopIteration` signalling no more rows
are available.
"""
if self.current > self.stop:
raise StopIteration
ret, self.current = self.current, self.current + self.step
return (ret,)
# Usage:
cursor = db.execute_sql('SELECT * FROM series(?, ?, ?)', (0, 5, 2))
for value, in cursor:
print(value)
# Prints:
# 0
# 2
# 4
事务的锁定模式¶
SQLite 事务可以以三种模式打开:
延迟(**默认**)- 仅在执行读或写操作时获取锁。第一次读取会创建一个 共享锁,第一次写入会创建一个 保留锁。由于锁的获取是延迟到实际需要时才进行,因此其他线程或进程有可能创建单独的事务并写入数据库。
立即- 立即获取一个 保留锁。在此模式下,没有其他连接可以写入数据库或打开一个 *立即* 或 *独占* 事务。但是,其他进程可以继续从数据库读取。
独占- 打开一个 独占锁,阻止所有(除了读未提交)连接访问数据库,直到事务完成。
指定锁定模式的示例
db = SqliteDatabase('app.db')
with db.atomic('EXCLUSIVE'):
read()
write()
@db.atomic('IMMEDIATE')
def some_other_function():
# This function is wrapped in an "IMMEDIATE" transaction.
do_something_else()
有关更多信息,请参阅 SQLite 的 锁定文档。要了解 Peewee 中事务的更多信息,请参阅 事务 文档。
危险
请勿修改 sqlite3.Connection 对象的 `isolation_level` 属性。Peewee 要求 sqlite3 驱动程序处于自动提交模式,这由 SqliteDatabase 自动处理。
CySqlite¶
CySqliteDatabase 使用 cysqlite 驱动,这是标准库 sqlite3 模块的高性能替代品。cysqlite 提供了标准库 sqlite3 驱动程序中没有的附加功能和钩子。
安装
pip install cysqlite
用法
from playhouse.cysqlite_ext import CySqliteDatabase
db = CySqliteDatabase('my_app.db', pragmas={
'cache_size': -64000,
'journal_mode': 'wal',
'foreign_keys': 1,
})
- class CySqliteDatabase(database, **kwargs)¶
- 参数
pragmas (list) – 一个 2 元组列表,包含 pragma 键值对,每次打开连接时都会设置。
timeout – 设置 SQLite 驱动程序的繁忙超时(以秒为单位)。
rank_functions (bool) – 使搜索结果排名函数可用。仅在使用 FTS4 时推荐。
regexp_function (bool) – 使 REGEXP 函数可用。
另请参阅
CySqliteDatabase 继承自
SqliteDatabase,并继承了所有用于声明用户定义函数、聚合函数、窗口函数、排序规则、pragma 等的方法。示例
db = CySqliteDatabase('app.db', pragmas={'journal_mode': 'wal'})
- table_function(name)¶
用于注册 `cysqlite.TableFunction` 的类装饰器。表函数是用户定义的函数,它们不返回单个标量值,而是可以返回任意数量的表格式数据行。
有关 `TableFunction` API 的详细信息,请参阅 cysqlite 文档。
from cysqlite import TableFunction @db.table_function('series') class Series(TableFunction): columns = ['value'] params = ['start', 'stop', 'step'] def initialize(self, start=0, stop=None, step=1): """ Table-functions declare an initialize() method, which is called with whatever arguments the user has called the function with. """ self.start = self.current = start self.stop = stop or float('Inf') self.step = step def iterate(self, idx): """ Iterate is called repeatedly by the SQLite database engine until the required number of rows has been read **or** the function raises a `StopIteration` signalling no more rows are available. """ if self.current > self.stop: raise StopIteration ret, self.current = self.current, self.current + self.step return (ret,) cursor = db.execute_sql('SELECT * FROM series(?, ?, ?)', (0, 5, 2)) for (value,) in cursor: print(value) # Prints: # 0 # 2 # 4
- register_table_function(klass, name)¶
- 参数
klass (TableFunction) – 实现 TableFunction API 的类。
name (str) – 用户定义表函数的名称。
将 `cysqlite.TableFunction` 类注册到连接。表函数是用户定义的函数,它们不返回单个标量值,而是可以返回任意数量的表格式数据行。
另请参阅
有关示例实现,请参阅
CySqliteDatabase.table_function()。有关 `TableFunction` API 的详细信息,请参阅 cysqlite 文档。
- unregister_table_function(name)¶
- 参数
name – 用户定义表函数的名称。
- 返回
如果成功移除函数,则返回 True,否则返回 False。
注销用户定义的表函数。
- on_commit(fn)¶
- 参数
fn – 可调用对象或 `None` 以清除当前钩子。
注册一个回调函数,该函数将在当前连接提交事务时执行。回调函数不接受任何参数,返回值将被忽略。
但是,如果回调函数引发 `ValueError`,事务将被中止并回滚。
示例
db = CySqliteDatabase(':memory:') @db.on_commit def on_commit(): logger.info('COMMITing changes')
- on_rollback(fn)¶
- 参数
fn – 可调用对象或 `None` 以清除当前钩子。
注册一个回调函数,该函数将在当前连接回滚事务时执行。回调函数不接受任何参数,返回值将被忽略。
示例
@db.on_rollback def on_rollback(): logger.info('Rolling back changes')
- on_update(fn)¶
- 参数
fn – 可调用对象或 `None` 以清除当前钩子。
注册一个回调函数,该函数将在数据库被写入(通过 `UPDATE`、`INSERT` 或 `DELETE` 查询)时执行。回调函数应接受以下参数:
query- 查询类型,即 `INSERT`、`UPDATE` 或 `DELETE`。database name - 默认数据库名为 `main`。
table name - 正在修改的表的名称。
rowid - 正在修改的行的 `rowid`。
回调函数的返回值将被忽略。
示例
db = CySqliteDatabase(':memory:') @db.on_update def on_update(query_type, db, table, rowid): # e.g. INSERT row 3 into table users. logger.info('%s row %s into table %s', query_type, rowid, table)
- authorizer(fn)¶
- 参数
fn – 可调用对象或 `None` 以清除当前授权程序。
注册一个授权程序回调。授权程序回调必须接受 5 个参数,这些参数因要检查的操作而异。
op: 操作代码,例如 `cysqlite.SQLITE_INSERT`。
p1: 操作特定值,例如 `SQLITE_INSERT` 的表名。
p2: 操作特定值。
p3: 数据库名称,例如 `"main"`。
p4: 访问尝试的内部触发器或视图(如果适用),否则为 `None`。
有关授权程序代码的描述以及参数 p1 和 p2 的值,请参阅 sqlite 授权程序文档。
授权程序回调必须返回以下之一:
cysqlite.SQLITE_OK: 允许操作。cysqlite.SQLITE_IGNORE: 允许语句编译,但阻止操作发生。cysqlite.SQLITE_DENY: 阻止语句编译。
有关更多详细信息,请参阅 cysqlite 文档。
- trace(fn, mask=2, expand_sql=True):
- 参数
fn – 可调用对象或 `None` 以清除当前跟踪钩子。
mask (int) – 要跟踪的事件类型的掩码。默认值对应于 `SQLITE_TRACE_PROFILE`。
expand_sql (bool) – 将 `sqlite3_stmt` 的 `sqlite3_expanded_sql()` 传递给回调(展开绑定参数)。
注册一个跟踪钩子(`sqlite3_trace_v2`)。跟踪回调必须接受 4 个参数,这些参数因要跟踪的操作而异。
event: 事件类型,例如 `SQLITE_TRACE_PROFILE`。
sid: 语句的内存地址(仅 `SQLITE_TRACE_CLOSE`),否则为 -1。
sql: SQL 字符串。如果 `expand_sql` 为 True,则绑定参数将被展开(对于 `SQLITE_TRACE_CLOSE`,`sql=None`)。
ns: 语句运行的估计纳秒数(仅 `SQLITE_TRACE_PROFILE`),否则为 -1。
回调返回的任何值都将被忽略。
有关更多详细信息,请参阅 cysqlite 文档。
- slow_query_log(threshold_ms=50, logger=None, level=logging.WARNING, expand_sql=True)¶
- 参数
threshold_ms – 记录慢查询的估计毫秒阈值。
logger – 日志记录命名空间,默认为 `'peewee.cysqlite_ext'`。
level (int) – 慢查询日志的级别。
expand_sql (bool) – 在 SQL 查询中展开绑定参数。
注册一个 `sqlite3_trace_v2` 回调,该回调会将慢查询记录到指定的日志记录器。将覆盖先前注册的 `trace()` 回调。在新连接打开时会自动重新注册。
- progress(fn, n=1)¶
- 参数
fn – 可调用对象或 `None` 以清除当前进度处理程序。
n (int) – 在调用进度处理程序之间要执行的 VM 指令的大约数量。
注册一个进度处理程序(`sqlite3_progress_handler`)。回调函数不接受任何参数,并返回 0 以允许进度继续,或返回任何非零值以中断进度。
有关更多详细信息,请参阅 cysqlite 文档。
- autocommit¶
返回一个布尔值的属性,指示是否启用了自动提交。默认情况下,此值将为 `True`,除非在事务(或 `atomic()` 块)内。
示例
>>> db = CySqliteDatabase(':memory:') >>> db.autocommit True >>> with db.atomic(): ... print(db.autocommit) ... False >>> db.autocommit True
- backup(destination, pages=None, name=None, progress=None)¶
- 参数
destination (CySqliteDatabase) – 将用作备份目标的数据库对象。
pages (int) – 每次迭代的页数。默认值 -1 表示所有页面都应在一个步骤中备份。
name (str) – 源数据库的名称(如果使用了 ATTACH DATABASE 加载了多个数据库,则可能不同)。默认为 “main”。
progress – 进度回调,使用三个参数调用:剩余页数、总页数以及备份是否完成。
示例
master = CySqliteDatabase('master.db') replica = CySqliteDatabase('replica.db') # Backup the contents of master to replica. master.backup(replica)
- backup_to_file(filename, pages, name, progress)¶
- 参数
filename – 用于存储数据库备份的文件名。
pages (int) – 每次迭代的页数。默认值 -1 表示所有页面都应在一个步骤中备份。
name (str) – 源数据库的名称(如果使用了 ATTACH DATABASE 加载了多个数据库,则可能不同)。默认为 “main”。
progress – 进度回调,使用三个参数调用:剩余页数、总页数以及备份是否完成。
将当前数据库备份到文件。备份的数据不是数据库转储,而是实际的 SQLite 数据库文件。
示例
db = CySqliteDatabase('app.db') def nightly_backup(): filename = 'backup-%s.db' % (datetime.date.today()) db.backup_to_file(filename)
- blob_open(table, column, rowid, read_only=False)¶
- 参数
table (str) – 包含数据的表的名称。
column (str) – 包含数据的列的名称。
rowid (int) – 要检索的行的 ID。
read_only (bool) – 以只读模式打开 blob。
dbname (str) – 数据库名称(例如,如果附加了多个数据库)。
- 返回
提供高效访问底层二进制数据的 `cysqlite.Blob` 实例。
- 返回类型
cysqlite.Blob
有关更多详细信息,请参阅 cysqlite 文档。
示例
class Image(Model): filename = TextField() data = BlobField() buf_size = 1024 * 1024 * 8 # Allocate 8MB for storing file. rowid = Image.insert({ Image.filename: 'thefile.jpg', Image.data: fn.zeroblob(buf_size), }).execute() # Open the blob, returning a file-like object. blob = db.blob_open('image', 'data', rowid) # Write some data to the blob. blob.write(image_data) img_size = blob.tell() # Read the data back out of the blob. blob.seek(0) image_data = blob.read(img_size)
- class PooledCySqliteDatabase(database, **kwargs)¶
连接池版本的 `CySqliteDatabase`。
APSW¶
APSW 是 SQLite C API 的一个精简 C 语言包装器,它暴露了几乎所有的 SQLite 功能,包括虚拟表、虚拟文件系统和 BLOB I/O。
安装
pip install apsw
用法
from playhouse.apsw_ext import APSWDatabase
db = APSWDatabase('my_app.db')
class BaseModel(Model):
class Meta:
database = db
请使用 `playhouse.apsw_ext` 中的 `Field` 子类,而不是 `peewee` 中的子类,以确保正确的类型适配。例如,使用 `playhouse.apsw_ext.DateTimeField` 而不是 `peewee.DateTimeField`。
- class APSWDatabase(database, **connect_kwargs)¶
使用 APSW 驱动的 `SqliteDatabase` 的子类。
- 参数
database (string) – sqlite 数据库文件名。
connect_kwargs – 打开连接时传递给 apsw 的关键字参数。
- register_module(mod_name, mod_inst)¶
全局注册一个虚拟表模块。请参阅 APSW 虚拟表文档。
- 参数
mod_name (string) – 要使用的模块名称。
mod_inst (object) – 一个实现 `Virtual Table` 接口的对象。
- unregister_module(mod_name)¶
注销之前注册的模块。
SQLCipher¶
SQLCipher 是 SQLite 的一个加密包装器。Peewee 通过 SqlCipherDatabase 暴露它,该类与 `SqliteDatabase` 的 API 相同,但构造函数除外。
安装
pip install sqlcipher3
用法
from playhouse.sqlcipher_ext import SqlCipherDatabase
db = SqlCipherDatabase(
'app.db',
passphrase=os.environ['PASSPHRASE'],
pragmas={'cache_size': -64000})
使用延迟初始化和密码提示的示例用法
db = SqlCipherDatabase(None)
class BaseModel(Model):
class Meta:
database = db
class Secret(BaseModel):
value = TextField()
# Prompt the user and initialize the database with their passphrase.
while True:
db.init('my_app.db', passphrase=input('Passphrase: '))
try:
db.get_tables() # Will raise if passphrase is wrong.
break
except DatabaseError as exc:
print('Wrong passphrase.')
db.init(None)
Pragma 配置(例如,增加 PBKDF2 迭代次数)
db = SqlCipherDatabase('my_app.db',
passphrase='s3cr3t',
pragmas={'kdf_iter': 1_000_000})
SQLCipher 可以使用许多扩展 PRAGMA 进行配置。PRAGMA 列表及其描述可在 SQLCipher 文档 中找到。
- class SqlCipherDatabase(database, passphrase, **kwargs)¶
- 参数
database (str) – 加密数据库文件的路径。
passphrase (str) – 加密密码(至少 8 个字符;在您的应用程序中强制要求更强的要求)。
如果数据库文件不存在,则会创建它并使用从 `passphrase` 派生的密钥进行加密。如果文件已存在,则 `passphrase` 必须与创建文件时使用的密码匹配。
如果密码不正确,在第一次尝试访问数据库时会引发错误(通常是 `DatabaseError: file is not a database`)。
- rekey(passphrase)¶
更改打开数据库的加密密码。
SqliteQueueDatabase¶
SqliteQueueDatabase 将所有写入查询通过一个长期运行的连接在一个专用的后台线程中串行化。这允许多个应用程序线程并发地写入 SQLite 数据库,而不会发生冲突或超时错误。
如果您想从多个线程对 SQLite 数据库进行简单的 **读写** 访问,并且不需要事务,可以将 `SqliteQueueDatabase` 用作普通 `SqliteDatabase` 的即插即用替换。
from playhouse.sqliteq import SqliteQueueDatabase
db = SqliteQueueDatabase(
'my_app.db',
use_gevent=False, # Use stdlib threading (default).
autostart=True, # Start the writer thread immediately.
queue_max_size=64, # Max pending writes before blocking.
results_timeout=5.0, # Seconds to wait for a write to complete.
pragmas={'journal_mode': 'wal'})
如果设置 `autostart=False`,则显式启动写入器线程
db.start()
在应用程序关闭时停止写入器线程(等待待处理写入完成)
import atexit
@atexit.register
def _stop():
db.stop()
读取查询正常工作。按需打开和关闭连接,就像使用任何其他数据库一样。只有写入操作会通过队列进行。
**不支持事务。** 因为不同线程的写入操作是交错的,所以无法保证一个线程的事务中的语句会原子地执行,而不会出现另一个线程的语句插入其中。如果调用 `atomic()` 和 `transaction()` 方法,将引发 `ValueError`。
如果您需要暂时绕过队列并直接写入(例如,在批量导入期间),请使用 `pause()` 和 `unpause()`。
- class SqliteQueueDatabase(database, use_gevent=False, autostart=True, queue_max_size=None, results_timeout=None, **kwargs)¶
- 参数
database (str) – 数据库文件名。
use_gevent (bool) – 使用 gevent 而不是 `threading`。
autostart (bool) – 自动启动后台写入线程。
queue_max_size (int) – 待处理写入队列的最大尺寸。
results_timeout (float) – 等待写入线程返回查询结果的超时时间(秒)。
- start()¶
启动后台写入线程。
- stop()¶
向写入线程发出停止信号。阻塞直到所有待处理写入都完成。
- is_stopped()¶
如果写入线程未运行,则返回 `True`。
- pause()¶
阻塞直到写入线程完成当前工作,然后断开其连接。调用线程将接管直接数据库访问。必须随后调用 `unpause()`。
- unpause()¶
恢复写入线程并重新连接队列。
SQLite 特有字段¶
这些字段类位于 `playhouse.sqlite_ext` 中,可与
- class RowIDField¶
映射到 SQLite 隐式 `rowid` 列的主键字段。
有关更多信息,请参阅 SQLite 关于 rowid 表 的文档。
class Note(Model): rowid = RowIDField() # Implied primary_key=True. content = TextField() timestamp = TimestampField()
RowIDField 可以映射到不同的字段名,但其底层列名始终是 `rowid`。
class Note(Model): id = RowIDField() ...
- class AutoIncrementField¶
整数主键,使用 SQLite 的 `AUTOINCREMENT` 关键字,保证主键即使在删除后也始终严格递增。与默认的 `PrimaryKeyField` 或 `RowIDField` 相比,性能略有下降。
有关详细信息,请参阅 SQLite AUTOINCREMENT 文档。
- class ISODateTimeField¶
DateTimeField 的子类,在存储到 SQLite 的基于文本的日期时间表示时,保留时区感知日期时间对象的 UTC 偏移信息。
- class TDecimalField(max_digits=10, decimal_places=5, auto_round=False, rounding=None, *args, **kwargs)¶
DecimalField 的子类,它将 decimal 值存储在 `TEXT` 列中,以避免在存储到 `REAL`(双精度浮点)列时可能发生的任何精度损失。SQLite 没有真正的数字类型,因此该字段确保在使用 Decimal 时不会丢失精度。
SQLite JSON¶
JSONField 使用 SQLite json 函数 实现对 JSON 数据的存储和查询。
- class JSONField(json_dumps=None, json_loads=None, **kwargs)¶
- 参数
json_dumps – 自定义 JSON 序列化器。默认为 `json.dumps`。
json_loads – 自定义 JSON 反序列化器。默认为 `json.loads`。
透明地存储和检索 JSON 数据,并为就地修改和查询提供高效的实现。数据在写入时自动序列化,在读取时反序列化。
示例模型
from peewee import * from playhouse.sqlite_ext import JSONField db = SqliteDatabase(':memory:') class Config(db.Model): data = JSONField() Config.create_table() # Create two rows. Config.create(data={'timeout': 30, 'retry': {'max': 5}}) Config.create(data={'timeout': 10, 'retry': {'max': 10}})
要访问或修改 JSON 结构中的特定对象键或数组索引,您可以像对待字典/列表一样对待 `JSONField`:
# Select or order by a JSON value: query = (Config .select(Config, Config.data['timeout'].alias('timeout')) .order_by(Config.data['timeout'].desc())) # Aggregate on nested value: avg = (Config .select(fn.SUM(Config.data['timeout']) / fn.COUNT(Config.id)) .scalar()) # Filter by nested value: Config.select().where(Config.data['retry']['max'] < 8)
数据可以就地原子地更新、写入和删除。
# In-place update (preserves other keys): (Config .update(data=Config.data.update({'timeout': 60})) .where(Config.data['timeout'] >= 30) .execute()) # Set a specific path: (Config .update(data=Config.data['timeout'].set(120)) .where(Config.data['retry']['max'] == 5) .execute()) # Update a specific path with an object. Existing field ("max") will be # preserved in this example. (Config .update(data=Config.data['retry'].update({'backoff': 1})) .execute()) # To overwrite a specific path with an object, use set(): (Config .update(data=Config.data['retry'].set({'allowed': 10})) .execute()) # Remove a key atomically: (Config .update(data=Config.data.update({'retry': None})) .where(Config.id == 1) .execute()) # Another way to remove atomically: (Config .update(data=Config.data['retry'].remove()) .where(Config.id == 2) .execute())
其他 JSON 场景的助手
# Query JSON types: query = (Config .select(Config.data.json_type(), Config.data['timeout'].json_type()) .tuples()) # [('object', 'integer'), ('object', 'integer')] # Query length of an array: cfg1 = Config.create(data={'statuses': [1, 99, 1, 1]}) cfg2 = Config.create(data={'statuses': [1, 1]}) query = (Config .select( Config.data['statuses'], Config.data['statuses'].length()) .tuples()) # [([1, 99, 1, 1], 4), ([1, 1], 2)]
让我们添加一个嵌套值,然后看看如何使用 `tree()` 方法递归地遍历其内容。
Config.create(data={'x1': {'y1': 'z1', 'y2': 'z2'}, 'x2': [1, 2]}) tree = Config.data.tree().alias('tree') query = (Config .select(Config.id, tree.c.fullkey, tree.c.value) .from_(Config, tree)) for row in query.tuples(): print(row) (1, '$', {'x1': {'y1': 'z1', 'y2': 'z2'}, 'x2': [1, 2]}), (1, '$.x2', [1, 2]), (1, '$.x2[0]', 1), (1, '$.x2[1]', 2), (1, '$.x1', {'y1': 'z1', 'y2': 'z2'}), (1, '$.x1.y1', 'z1'), (1, '$.x1.y2', 'z2')]
`tree()` 和 `children()` 方法非常强大。有关如何使用它们的更多信息,请参阅 SQLite 的 json1 扩展文档。
- __getitem__(item)¶
- 参数
item – 访问 JSON 数据中的特定键或数组索引。
- 返回
一个特殊的显式访问 JSON 数据的对象。
- 返回类型
访问 JSON 数据中的特定键或数组索引。返回一个 `JSONPath` 对象,该对象暴露了用于读取或修改 JSON 对象特定部分的便捷方法。
示例
# If metadata contains {"tags": ["list", "of", "tags"]}, we can # extract the first tag in this way: Post.select(Post, Post.metadata['tags'][0].alias('first_tag'))
更多示例请参阅
JSONPathAPI 文档。
- extract(*paths)¶
- 参数
paths – 一个或多个要提取的 JSON 路径。
提取一个或多个 JSON 路径值。当提供多个路径时,返回一个列表。
- extract_json(path)¶
- 参数
path (str) – JSON 路径。
以 JSON 数据类型提取指定路径的值。这对应于 SQLite 3.38 中添加的 `->` 运算符。
- extract_text(path)¶
- 参数
path (str) – JSON 路径。
以 SQL 数据类型提取指定路径的值。这对应于 SQLite 3.38 中添加的 `->>` 运算符。
- set(value, as_json=None)¶
- 参数
value – 标量值、列表或字典。
as_json (bool) – 强制将值视为 JSON,在这种情况下,它将在 Python 中预先序列化为 JSON。默认情况下,列表和字典被视为要序列化的 JSON,而字符串和整数则按原样传递。
设置存储在 `JSONField` 中的值。
使用 json1 扩展的 `json_set()` 函数。
- replace(value, as_json=None)¶
- 参数
value – 标量值、列表或字典。
as_json (bool) – 强制将值视为 JSON,在这种情况下,它将在 Python 中预先序列化为 JSON。默认情况下,列表和字典被视为要序列化的 JSON,而字符串和整数则按原样传递。
替换存储在 `JSONField` 中的现有值。如果不存在,则不会创建。
使用 json1 扩展的 `json_replace()` 函数。
- insert(value, as_json=None)¶
- 参数
value – 标量值、列表或字典。
as_json (bool) – 强制将值视为 JSON,在这种情况下,它将在 Python 中预先序列化为 JSON。默认情况下,列表和字典被视为要序列化的 JSON,而字符串和整数则按原样传递。
将值插入 `JSONField`。如果已存在,则不会覆盖。
使用 json1 扩展的 `json_insert()` 函数。
- append(value, as_json=None)¶
- 参数
value – 标量值、列表或字典。
as_json (bool) – 强制将值视为 JSON,在这种情况下,它将在 Python 中预先序列化为 JSON。默认情况下,列表和字典被视为要序列化的 JSON,而字符串和整数则按原样传递。
将值附加到存储在 `JSONField` 中的数组。
使用 json1 扩展的 `json_set()` 函数。
- update(data)¶
- 参数
data – 一个标量值、列表或字典,用于与 `JSONField` 中当前存储的数据合并。要删除某个键,请将其在更新的数据中设置为 `None`。
使用 RFC-7396 MergePatch 算法将补丁(`data` 参数)应用于列数据,从而将新数据合并到 JSON 值中。MergePatch 可以添加、修改或删除 JSON 对象的元素,这意味着 `update()` 是 `set()` 和 `remove()` 的通用替代方法。MergePatch 将 JSON 数组对象视为原子单元,因此 `update()` 无法向数组追加元素,也无法修改数组的单个元素。
有关更多信息和示例,请参阅 SQLite 的 json_patch() 函数文档。
- remove()¶
删除存储在 `JSONField` 中的数据。
使用 json1 扩展的 `json_remove` 函数。
- json_type()¶
返回一个字符串,标识存储在列中的值的类型。
返回的类型将是以下之一:
object
array
integer
real
true
false
text
null – 字符串 “null” 表示实际的 NULL 值
NULL – 实际的 NULL 值表示路径未找到
使用 json1 扩展的 `json_type` 函数。
- length()¶
返回存储在列中的数组的长度。
使用 json1 扩展的 `json_array_length` 函数。
- children()¶
`children` 函数对应于 `json_each`,这是一个表值函数,它遍历提供的 JSON 值并返回顶级数组或对象的直接子项。如果指定了路径,则该路径被视为最顶层的元素。
调用 `children()` 返回的行具有以下属性:
key: 当前元素相对于其父级的键。value: 当前元素的值。type: 数据类型之一(请参阅 `json_type()`)。atom: 原始类型的值,数组和对象的 `NULL`。id: 引用树中当前节点的唯一 ID。parent: 包含节点的 ID。fullkey: 描述当前元素的完整路径。path: 当前行的容器的路径。
此方法在内部使用 json1 扩展的 `json_each`(文档链接)函数。
用法示例(与 `tree()` 方法比较)
class KeyData(Model): key = TextField() data = JSONField() KeyData.create(key='a', data={'k1': 'v1', 'x1': {'y1': 'z1'}}) KeyData.create(key='b', data={'x1': {'y1': 'z1', 'y2': 'z2'}}) # We will query the KeyData model for the key and all the # top-level keys and values in it's data field. kd = KeyData.data.children().alias('children') query = (KeyData .select(kd.c.key, kd.c.value, kd.c.fullkey) .from_(KeyData, kd) .order_by(kd.c.key) .tuples()) print(query[:]) # PRINTS: [('a', 'k1', 'v1', '$.k1'), ('a', 'x1', '{"y1":"z1"}', '$.x1'), ('b', 'x1', '{"y1":"z1","y2":"z2"}', '$.x1')]
- tree()¶
`tree` 函数对应于 `json_tree`,这是一个表值函数,它递归地遍历提供的 JSON 值并返回每个级别键的信息。如果指定了路径,则该路径被视为最顶层的元素。
调用 `tree()` 返回的行具有与调用 `children()` 返回的行相同的属性。
key: 当前元素相对于其父级的键。value: 当前元素的值。type: 数据类型之一(请参阅 `json_type()`)。atom: 原始类型的值,数组和对象的 `NULL`。id: 引用树中当前节点的唯一 ID。parent: 包含节点的 ID。fullkey: 描述当前元素的完整路径。path: 当前行的容器的路径。
此方法在内部使用 json1 扩展的 `json_tree`(文档链接)函数。
使用示例
class KeyData(Model): key = TextField() data = JSONField() KeyData.create(key='a', data={'k1': 'v1', 'x1': {'y1': 'z1'}}) KeyData.create(key='b', data={'x1': {'y1': 'z1', 'y2': 'z2'}}) # We will query the KeyData model for the key and all the # keys and values in it's data field, recursively. kd = KeyData.data.tree().alias('tree') query = (KeyData .select(kd.c.key, kd.c.value, kd.c.fullkey) .from_(KeyData, kd) .order_by(kd.c.key) .tuples()) print(query[:]) # PRINTS: [('a', None, '{"k1":"v1","x1":{"y1":"z1"}}', '$'), ('b', None, '{"x1":{"y1":"z1","y2":"z2"}}', '$'), ('a', 'k1', 'v1', '$.k1'), ('a', 'x1', '{"y1":"z1"}', '$.x1'), ('b', 'x1', '{"y1":"z1","y2":"z2"}', '$.x1'), ('a', 'y1', 'z1', '$.x1.y1'), ('b', 'y1', 'z1', '$.x1.y1'), ('b', 'y2', 'z2', '$.x1.y2')]
- class JSONPath(field, path=None)¶
- 参数
field (JSONField) – 我们打算访问的字段对象。
path (tuple) – 组成 JSON 路径的组件。
一种便捷的 Pythonic 表示 JSON 路径的方式,用于 `JSONField`。它实现了与 `JSONField` 相同的接口,但专为操作嵌套项而设计,例如:
Config.create(data={'timeout': 30, 'retries': {'max': 5}}) # Both Config.data['timeout'] and Config.data['retries']['max'] # are instances of JSONPath: query = (Config .select(Config.data['timeout']) .where(Config.data['retries']['max'] < 10))
- class JSONBField(json_dumps=None, json_loads=None, **kwargs)¶
扩展自
JSONField,并将数据存储为二进制jsonb格式(SQLite 3.45.0+)。读取原始列值时,数据是其编码的二进制形式,请使用json()方法进行解码。# Raw read returns binary: kv = KV.get(KV.key == 'a') kv.value # b"l'k1'v1" # Use .json() to get a Python object: kv = KV.select(KV.value.json()).get() kv.value # {'k1': 'v1'}
- json()¶
指示 JSONB 字段数据应被反序列化并作为 JSON 返回(而不是 SQLite 二进制格式)。
全文搜索¶
Peewee 支持 FTS3、FTS4(遗留,广泛可用)和 FTS5 全文搜索扩展。
通用模式是
定义一个
FTSModel或FTS5Model子类,其中包含一个或多个SearchField列。当源表中的行被创建或更新时,在搜索索引中插入或更新相应的行。
查阅 SQLite 文档以了解 FTS 查询语法图。
- class SearchField(unindexed=False, column_name=None)¶
用于全文搜索虚拟表的字段类型。如果指定了约束(
null=False、unique=True等),则会引发异常,因为 FTS 表不支持它们。通过
unindexed=True来存储元数据,同时不索引搜索索引。class DocumentIndex(FTSModel): title = SearchField() content = SearchField() tags = SearchField() timestamp = SearchField(unindexed=True)
- match(term)¶
- 参数
term (str) – 全文搜索查询/词条。
- 返回
对应于
MATCH运算符的Expression。
SQLite 的全文搜索支持搜索整个表(包括所有已索引的列)**或**搜索单个列。
match()方法可用于将搜索限制为单个列。# Search *only* the title field and return results ordered by # relevance, using bm25. query = (DocumentIndex .select(DocumentIndex, DocumentIndex.bm25().alias('score')) .where(DocumentIndex.title.match('python')) .order_by(DocumentIndex.bm25()))
要搜索*所有*已索引的列,请使用
FTSModel.match()方法。# Searches *both* the title and body and return results ordered by # relevance, using bm25. query = (DocumentIndex .select(DocumentIndex, DocumentIndex.bm25().alias('score')) .where(DocumentIndex.match('python')) .order_by(DocumentIndex.bm25()))
- highlight(left, right)¶
- 参数
left (str) – 用于高亮显示的开始标签,例如
'<b>'right (str) – 用于高亮显示的结束标签,例如
'</b>'
在使用
MATCH运算符进行搜索时,FTS5 可以返回给定列中匹配文本的高亮显示。# Search for items matching string 'python' and return the title # highlighted with square brackets. query = (SearchIndex .search('python') .select(SearchIndex.title.highlight('[', ']').alias('hi'))) for result in query: print(result.hi) # For example, might print: # Learn [python] the hard way
- snippet(left, right, over_length='...', max_tokens=16)¶
- 参数
left (str) – 用于高亮显示的开始标签,例如
'<b>'right (str) – 用于高亮显示的结束标签,例如
'</b>'over_length (str) – 当摘要超过最大 token 数时要添加的前导或尾部文本。
max_tokens (int) – 返回的最大 token 数,**必须是 1 - 64**。
在使用
MATCH运算符进行搜索时,FTS5 可以返回给定列中包含高亮显示匹配项的摘要文本。# Search for items matching string 'python' and return the title # highlighted with square brackets. query = (SearchIndex .search('python') .select(SearchIndex.title.snippet('[', ']').alias('snip'))) for result in query: print(result.snip)
FTS4 / FTSModel¶
FTSModel 使 Peewee 应用程序能够使用 SQLite FTS4 将数据存储在高效的全文本搜索索引中。
FTSModel 的注意事项
除
MATCH和rowid查找之外的所有查询都需要完全表扫描。不支持约束、外键和索引。
所有列都被视为
TEXT。没有内置排名。Peewee 提供了几种实现,可以通过将
rank_functions=True传递给SqliteDatabase(...)来自动注册。FTSModel 的
rowid主键可以使用RowIDField声明。对rowid的查找非常高效。
鉴于这些约束,除 rowid 之外的所有字段都应为 SearchField 的实例,以确保正确性。
提示
由于缺乏二级索引,通常将 FTSModel.rowid 主键视为正常 SQLite 表中行的外键是有意义的。
示例
from peewee import *
from playhouse.sqlite_ext import FTSModel, SearchField
db = SqliteDatabase('app.db', rank_functions=True)
class Document(Model):
# Canonical source of data, stored in a normal table.
author = ForeignKeyField(User, backref='documents')
title = TextField(null=False, unique=True)
content = TextField(null=False)
timestamp = DateTimeField()
class Meta:
database = db
class DocumentIndex(FTSModel):
# Full-text search index.
rowid = RowIDField()
title = SearchField()
content = SearchField()
author = SearchField(unindexed=True)
class Meta:
database = db
# Use the porter stemming algorithm to tokenize content, optimize
# prefix searches of 3 or 4 characters.
options = {'tokenize': 'porter unicode61', 'prefix': [3, 4]}
通过将数据插入 FTS 表来存储数据。
# Store a document in the index:
DocumentIndex.create(
rowid=document.id, # Set rowid to match Document's id.
title=document.title,
content=document.content,
author=document.author.get_full_name())
# Equivalent:
(DocumentIndex
.insert({
'rowid': document.id,
'title': document.title,
'content': document.content,
'author': document.author.get_full_name()})
.execute())
FTSModel 提供了几个用于全文搜索查询的快捷方式。
# Simple search using basic ranking algorithm.
results = DocumentIndex.search('python sqlite')
# BM25 search With score and per-column weighting:
results = DocumentIndex.search_bm25(
'python sqlite',
weights={'title': 2.0, 'content': 1.0},
with_score=True,
score_alias='relevance')
for r in results:
print(r.title, r.relevance)
一种重要的搜索方法依赖于索引数据的 rowid 与文档的规范 ID 相匹配。使用这种技术,我们可以应用额外的过滤器并高效地检索匹配的 Document 对象。
# Search and ensure we only retrieve articles from the last 30 days.
cutoff = datetime.datetime.now() - datetime.timedelta(days=30)
query = (Document
.select()
.join(
DocumentIndex,
on=(Document.id == DocumentIndex.rowid))
.where(
(Document.timestamp >= cutoff) &
DocumentIndex.match('python sqlite'))
.order_by(DocumentIndex.bm25()))
警告
除全文搜索和 rowid 查找外,FTSModel 类的所有 SQL 查询都将是全表扫描。
外部内容
如果正在索引的内容的主要来源存在于单独的表中,则可以通过指示 SQLite 不存储搜索索引内容的额外副本,来节省一些磁盘空间。
要实现这一点,您可以指定一个表,使用 content 选项。FTS4 文档和FTS5 文档提供了更多信息。
以下是一个简短的示例,说明了如何使用 peewee 实现这一点。
class Blog(Model):
title = TextField()
pub_date = DateTimeField(default=datetime.datetime.now)
content = TextField() # We want to search this.
class Meta:
database = db
class BlogIndex(FTSModel): # or FTS5Model.
content = SearchField()
class Meta:
database = db
options = {
'content': Blog, # Data source.
'content_rowid': Blog.id, # FTS5 only.
}
db.create_tables([Blog, BlogIndex])
# Now, we can manage content in the BlogIndex. To populate the
# search index:
BlogIndex.rebuild()
# Optimize the index.
BlogIndex.optimize()
该 content 选项接受一个 Model,并且可以减少数据库使用的存储量,但代价是需要更仔细地保持数据同步。
- class FTSModel¶
适用于处理 SQLite FTS3 / FTS4 的基本 Model 类。
支持以下选项:
content: 包含外部内容的Model,或空字符串表示“无内容”。prefix: 整数。例如:'2' 或 '2,3,4'tokenize: simple, porter, unicode61。例如:'porter'
示例
class DocumentIndex(FTSModel): title = SearchField() body = SearchField() class Meta: database = db options = { 'tokenize': 'porter unicode61', 'prefix': '3', }
- classmethod match(term)¶
- 参数
term – 搜索词或表达式。FTS 语法文档。
生成一个 SQL 表达式,表示在表中搜索给定的词条或表达式。SQLite 使用
MATCH运算符来表示全文搜索。示例
# Search index for "search phrase" and return results ranked # by relevancy using the BM25 algorithm. query = (DocumentIndex .select() .where(DocumentIndex.match('search phrase')) .order_by(DocumentIndex.bm25())) for result in query: print('Result: %s' % result.title)
- classmethod search(term, weights=None, with_score=False, score_alias='score', explicit_ordering=False)¶
- 参数
term – 搜索词或表达式。FTS 语法文档。
weights – 列权重的列表,按表中列的顺序排列。**或者**,一个由字段或字段名作为键,值为映射的字典。
with_score – 分数是否作为
SELECT语句的一部分返回。score_alias (str) – 用于计算排名分数的别名。如果
with_score=True,您将使用此属性来访问分数。explicit_ordering (bool) – 使用完整的 SQL 函数来计算排名进行排序,而不是简单地在 ORDER BY 子句中引用分数别名。
搜索词条并按匹配质量排序结果的简写方法。
此方法使用简化的算法来确定结果的相关性排名。要获得更复杂的排名,请使用
search_bm25()方法。# Simple search. docs = DocumentIndex.search('search term') for result in docs: print(result.title) # More complete example. docs = DocumentIndex.search( 'search term', weights={'title': 2.0, 'content': 1.0}, with_score=True, score_alias='search_score') for result in docs: print(result.title, result.search_score)
- classmethod search_bm25(term, weights=None, with_score=False, score_alias='score', explicit_ordering=False)¶
- 参数
term – 搜索词或表达式。FTS 语法文档。
weights – 列权重的列表,按表中列的顺序排列。**或者**,一个由字段或字段名作为键,值为映射的字典。
with_score – 分数是否作为
SELECT语句的一部分返回。score_alias (str) – 用于计算排名分数的别名。如果
with_score=True,您将使用此属性来访问分数。explicit_ordering (bool) – 使用完整的 SQL 函数来计算排名进行排序,而不是简单地在 ORDER BY 子句中引用分数别名。
使用 BM25 算法搜索词条并按匹配质量排序结果的简写方法。
注意
BM25 排名算法仅适用于 FTS4。如果您使用的是 FTS3,请改用
search()方法。
- classmethod search_bm25f(term, weights=None, with_score=False, score_alias='score', explicit_ordering=False)¶
与
FTSModel.search_bm25()相同,但使用 BM25 排名算法的 BM25f 变体。
- classmethod search_lucene(term, weights=None, with_score=False, score_alias='score', explicit_ordering=False)¶
与
FTSModel.search_bm25()相同,但使用 Lucene 搜索引擎的结果排名算法。
- classmethod rank(col1_weight, col2_weight...coln_weight)¶
- 参数
col_weight (float) –(可选)为模型的第 *i* 列指定的权重。默认情况下,所有列的权重均为
1.0。
生成一个表达式,用于计算和返回搜索匹配的质量。此
rank可用于对搜索结果进行排序。rank函数接受可选参数,允许您为各种列指定权重。如果未指定权重,则所有列的重要性相同。rank()使用的算法很简单且相对快速。要获得更复杂的排名,请使用query = (DocumentIndex .select( DocumentIndex, DocumentIndex.rank().alias('score')) .where(DocumentIndex.match('search phrase')) .order_by(DocumentIndex.rank())) for search_result in query: print(search_result.title, search_result.score)
- classmethod bm25(col1_weight, col2_weight...coln_weight)¶
- 参数
col_weight (float) –(可选)为模型的第 *i* 列指定的权重。默认情况下,所有列的权重均为
1.0。
生成一个表达式,用于使用 BM25 算法计算和返回搜索匹配的质量。此值可用于对搜索结果进行排序。
与
rank()类似,bm25函数接受可选参数,允许您为各种列指定权重。如果未指定权重,则所有列的重要性相同。BM25 结果排名算法需要 FTS4。如果您使用的是 FTS3,请改用
rank()。query = (DocumentIndex .select( DocumentIndex, DocumentIndex.bm25().alias('score')) .where(DocumentIndex.match('search phrase')) .order_by(DocumentIndex.bm25())) for search_result in query: print(search_result.title, search_result.score)
上面的代码示例等同于调用
search_bm25()方法。query = DocumentIndex.search_bm25('search phrase', with_score=True) for search_result in query: print(search_result.title, search_result.score)
- classmethod rebuild()¶
重建搜索索引。仅当指定了
content选项(内容表)时才有效。
- classmethod optimize()¶
优化索引。
FTS5 / FTS5Model¶
FTS5Model 使 Peewee 应用程序能够使用 SQLite FTS5 将数据存储在高效的全文本搜索索引中。FTS5 还附带了原生的 BM25 结果排名。
FTS5Model 的注意事项
除
MATCH和rowid查找之外的所有查询都需要完全表扫描。不支持约束、外键和索引。所有列**必须**是
SearchField的实例。FTS5Model 的
rowid主键可以使用RowIDField声明。对rowid的查找非常高效。
提示
由于缺乏二级索引,通常将 FTS5Model.rowid 主键视为正常 SQLite 表中行的外键是有意义的。
示例
from peewee import *
from playhouse.sqlite_ext import FTS5Model, SearchField
db = SqliteDatabase('app.db')
class Document(Model):
# Canonical source of data, stored in a normal table.
author = ForeignKeyField(User, backref='documents')
title = TextField(null=False, unique=True)
content = TextField(null=False)
timestamp = DateTimeField()
class Meta:
database = db
class DocumentIndex(FTS5Model):
# Full-text search index.
rowid = RowIDField()
title = SearchField()
content = SearchField()
author = SearchField(unindexed=True)
class Meta:
database = db
# Use the porter stemming algorithm and unicode tokenizers,
# and optimize prefix matches of 3 or 4 characters.
options = {'tokenize': 'porter unicode61', 'prefix': [3, 4]}
# Check that FTS5 is available:
if not DocumentIndex.fts5_installed():
raise RuntimeError('FTS5 is not available in this SQLite build.')
通过将数据插入 FTS5 表来存储数据。
# Store a document in the index:
DocumentIndex.create(
rowid=document.id, # Set rowid to match Document's id.
title=document.title,
content=document.content,
author=document.author.get_full_name())
# Equivalent:
(DocumentIndex
.insert({
'rowid': document.id,
'title': document.title,
'content': document.content,
'author': document.author.get_full_name()})
.execute())
FTS5Model 提供了几个用于全文搜索查询的快捷方式。
# Simple search (BM25, ordered by relevance):
results = DocumentIndex.search('python sqlite')
# With score and per-column weighting:
results = DocumentIndex.search(
'python sqlite',
weights={'title': 2.0, 'content': 1.0},
with_score=True,
score_alias='relevance')
for r in results:
print(r.title, r.relevance)
# Highlight matches in the title:
for r in (DocumentIndex.search('python')
.select(DocumentIndex.title.highlight('[', ']').alias('hi'))):
print(r.hi) # e.g. "Learn [python] the hard way"
提示
一种重要的搜索方法依赖于索引数据的 rowid 与文档的规范 ID 相匹配。使用这种技术,我们可以应用额外的过滤器并高效地检索匹配的 Document 对象。
# Search and ensure we only retrieve articles from the last 30 days.
cutoff = datetime.datetime.now() - datetime.timedelta(days=30)
query = (Document
.select()
.join(
DocumentIndex,
on=(Document.id == DocumentIndex.rowid))
.where(
(Document.timestamp >= cutoff) &
DocumentIndex.match('python sqlite'))
.order_by(DocumentIndex.rank()))
如果正在索引的内容的主要来源存在于单独的表中,则可以通过指示 SQLite 不存储搜索索引内容的额外副本,来节省一些磁盘空间。有关实现细节,请参阅外部内容。FTS5 文档提供了更多信息。
- class FTS5Model¶
继承了所有
FTSModel方法,此外还有:支持以下选项:
content: 包含外部内容的Model,或空字符串表示“无内容”。content_rowid:Field(外部内容主键)prefix: 整数。例如:'2' 或[2, 3]tokenize: simple, porter, unicode61。例如:'porter unicode61'
示例
class DocumentIndex(FTS5Model): title = SearchField() body = SearchField() class Meta: database = db options = { 'tokenize': 'porter unicode61', 'prefix': '3', }
- classmethod fts5_installed()¶
如果 FTS5 可用,则返回
True。
- classmethod match(term)¶
- 参数
term – 搜索词或表达式。FTS5 语法文档。
生成一个 SQL 表达式,表示在表中搜索给定的词条或表达式。SQLite 使用
MATCH运算符来表示全文搜索。示例
# Search index for "search phrase" and return results ranked # by relevancy using the BM25 algorithm. query = (DocumentIndex .select() .where(DocumentIndex.match('search phrase')) .order_by(DocumentIndex.rank())) for result in query: print('Result: %s' % result.title)
- classmethod search(term, weights=None, with_score=False, score_alias='score')¶
- 参数
term – 搜索词或表达式。FTS5 语法文档。
weights – 列权重的列表,按表中列的顺序排列。**或者**,一个由字段或字段名作为键,值为映射的字典。
with_score – 分数是否作为
SELECT语句的一部分返回。score_alias (str) – 用于计算排名分数的别名。如果
with_score=True,您将使用此属性来访问分数。explicit_ordering (bool) – 使用完整的 SQL 函数来计算排名进行排序,而不是简单地在 ORDER BY 子句中引用分数别名。
搜索词条并按匹配质量排序结果的简写方法。
FTS5扩展提供了 BM25 算法的内置实现,用于按相关性对结果进行排名。# Simple search. docs = DocumentIndex.search('search term') for result in docs: print(result.title) # More complete example. docs = DocumentIndex.search( 'search term', weights={'title': 2.0, 'content': 1.0}, with_score=True, score_alias='search_score') for result in docs: print(result.title, result.search_score)
- classmethod search_bm25(term, weights=None, with_score=False, score_alias='score')¶
对于 FTS5,
search_bm25()与search()方法相同。
- classmethod rank(col1_weight, col2_weight...coln_weight)¶
- 参数
col_weight (float) –(可选)为模型的第 *i* 列指定的权重。默认情况下,所有列的权重均为
1.0。
生成一个表达式,用于使用 BM25 算法计算和返回搜索匹配的质量。此值可用于对搜索结果进行排序。
rank函数接受可选参数,允许您为各种列指定权重。如果未指定权重,则所有列的重要性相同。query = (DocumentIndex .select( DocumentIndex, DocumentIndex.rank().alias('score')) .where(DocumentIndex.match('search phrase')) .order_by(DocumentIndex.rank())) for search_result in query: print(search_result.title, search_result.score)
上面的代码示例等同于调用
search()方法。query = DocumentIndex.search('search phrase', with_score=True) for search_result in query: print(search_result.title, search_result.score)
- classmethod bm25(col1_weight, col2_weight...coln_weight)¶
因为 FTS5 提供了对 BM25 的内置支持,所以此方法与
rank()方法相同。
- classmethod VocabModel(table_type='row' | 'col' | 'instance', table_name=None)¶
- 参数
table_type (str) – 'row'、'col' 或 'instance'。
table_name – 词汇表名称。如果未指定,则为“fts5tablename_v”。
生成一个 Model 类,用于访问与 FTS5 搜索索引对应的 vocab 表。
- classmethod rebuild()¶
重建搜索索引。仅当指定了
content选项(内容表)时才有效。
- classmethod optimize()¶
优化索引。
用户定义的函数集合¶
playhouse.sqlite_udf 包含一组函数、聚合和表值函数,这些函数按名称分组到集合中。
from playhouse.sqlite_udf import register_all, register_groups
from playhouse.sqlite_udf import DATE, STRING
db = SqliteDatabase('my_app.db')
register_all(db) # Register every function.
register_groups(db, DATE, STRING) # Register selected groups.
# Register individual functions:
from playhouse.sqlite_udf import gzip, gunzip
db.register_function(gzip, 'gzip')
db.register_function(gunzip, 'gunzip')
注册后,通过 Peewee 的 fn 命名空间或原始 SQL 调用函数。
# Find most common URL hostnames.
query = (Link
.select(fn.hostname(Link.url).alias('host'), fn.COUNT(Link.id))
.group_by(fn.hostname(Link.url))
.order_by(fn.COUNT(Link.id).desc())
.tuples())
可用函数¶
CONTROL_FLOW
- if_then_else(cond, truthy, falsey=None)¶
简单的三元运算符,根据
cond参数的真值,返回truthy或falsey值。
DATE
- strip_tz(date_str)¶
- 参数
date_str – 一个日期时间,编码为字符串。
- 返回
带有时区信息被剥离的日期时间。
时间不会以任何方式调整,只是移除了时区。
- humandelta(nseconds, glue=', ')¶
- 参数
nseconds (int) – 总秒数,在 timedelta 中。
glue (str) – 连接值的片段。
- 返回
易于阅读的 timedelta 描述。
例如,86471 -> “1 天, 1 分钟, 11 秒”
- mintdiff(datetime_value)¶
- 参数
datetime_value – 一个日期时间。
- 返回
列表中任何两个值之间的最小差值。
聚合:任意两个日期时间之间的最小差值。
- avgtdiff(datetime_value)¶
- 参数
datetime_value – 一个日期时间。
- 返回
列表中值之间的平均差值。
聚合:连续值之间的平均差值。
- duration(datetime_value)¶
- 参数
datetime_value – 一个日期时间。
- 返回
列表中从最小到最大的值之间的持续时间,以秒为单位。
聚合:从最小值到最大值的持续时间,以秒为单位。
- date_series(start, stop, step_seconds=86400)¶
- 参数
start (datetime) – 开始日期时间
stop (datetime) – 停止日期时间
step_seconds (int) – 构成一步的秒数。
表值函数:返回由从开始到停止迭代遇到的日期/时间值组成的行,
step_seconds一次。此外,如果开始时间没有时间部分且 step_seconds 大于或等于一天(86400 秒),则返回的值将是日期。反之,如果开始时间没有日期部分,则返回值将是时间。否则,返回值将是日期时间。
示例
SELECT * FROM date_series('2017-01-28', '2017-02-02'); value ----- 2017-01-28 2017-01-29 2017-01-30 2017-01-31 2017-02-01 2017-02-02
FILE
- file_ext(filename)¶
- 参数
filename (str) – 要从中提取扩展名的文件名。
- 返回
返回文件扩展名,包括开头的“.”。
- file_read(filename)¶
- 参数
filename (str) – 要读取的文件名。
- 返回
文件内容。
HELPER
- gzip(data, compression=9)¶
- 参数
data (bytes) – 要压缩的数据。
compression (int) – 压缩级别(9 为最高)。
- 返回
压缩后的二进制数据。
- gunzip(data)¶
- 参数
data (bytes) – 压缩数据。
- 返回
未压缩的二进制数据。
- hostname(url)¶
- 参数
url (str) – 要从中提取主机名的 URL。
- 返回
URL 的主机名部分。
- toggle(key)¶
- 参数
key – 要切换的键。
在 True/False 状态之间切换键。例如:
>>> toggle('my-key') True >>> toggle('my-key') False >>> toggle('my-key') True
- setting(key, value=None)¶
- 参数
key – 要设置/检索的键。
value – 要设置的值。
- 返回
与键关联的值。
在应用程序的生命周期内存储/检索内存中的设置并使其持久化。要获取当前值,请指定键。要设置新值,请使用键和新值调用。
MATH
- randomrange(start, stop=None, step=None)¶
- 参数
start (int) – 范围的开始(包含)
end (int) – 范围的结束(不包含)
step (int) – 返回值的间隔。
返回一个介于
[start, end)之间的随机整数。
- gauss_distribution(mean, sigma)¶
- 参数
mean (float) – 平均值
sigma (float) – 标准差
- sqrt(n)¶
计算
n的平方根。
- tonumber(s)¶
- 参数
s (str) – 要转换为数字的字符串。
- 返回
整数、浮点数或在失败时为 NULL。
- mode(val)¶
- 参数
val – 列表中的数字。
- 返回
出现次数最多(最常见)的数字。
聚合:计算值的*众数*。
- minrange(val)¶
- 参数
val – 值
- 返回
两个值之间的最小差值。
聚合:序列中两个数字之间的最小距离。
- avgrange(val)¶
- 参数
val – 值
- 返回
值之间的平均差值。
聚合:序列中连续数字之间的平均距离。
- range(val)¶
- 参数
val – 值
- 返回
序列中从最小值到最大值的范围。
聚合:观察到的值的范围。
- median(val)¶
- 参数
val – 值
- 返回
序列中的中位数(中间)值。
聚合:序列的中位数。
注意
仅在编译了
_sqlite_udf扩展时可用。
STRING
- substr_count(haystack, needle)¶
返回
needle在haystack中出现的次数。
- strip_chars(haystack, chars)¶
从
haystack的开头和结尾剥离chars中的任何字符。
- damerau_levenshtein_dist(s1, s2)¶
使用 Levenshtein 算法的 Damerau 变体计算从 s1 到 s2 的编辑距离。
注意
仅在编译了
_sqlite_udf扩展时可用。
- levenshtein_dist(s1, s2)¶
使用 Levenshtein 算法计算从 s1 到 s2 的编辑距离。
注意
仅在编译了
_sqlite_udf扩展时可用。
- str_dist(s1, s2)¶
使用标准库 SequenceMatcher 的算法计算从 s1 到 s2 的编辑距离。
注意
仅在编译了
_sqlite_udf扩展时可用。
- regex_search(regex, search_string)¶
- 参数
regex (str) – 正则表达式
search_string (str) – 要搜索 regex 匹配项的字符串。
表值函数:在字符串中搜索与提供的
regex匹配的子字符串。返回找到的每个匹配项的行。示例
SELECT * FROM regex_search('\w+', 'extract words, ignore! symbols'); value ----- extract words ignore symbols