目录
- 自定义 SQL 类型
- 定义类型
- 使用自定义类型
- 在 Dart 中
- 在 SQL 中
- 方言意识
- 支持的 SQLite 扩展
- json1
- fts5
- 地缘垄断
自定义 SQL 类型
Drift 的核心库主要以 SQLite3 为目标平台编写。这体现在Drift 开箱即用的SQL 类型上——这些类型由 SQLite3 支持,并新增了一些由 Dart 处理的类型。
其他 Drift 支持有限的数据库通常支持更多类型。例如,Postgres 为持续时间、JSON 值、UUID 等提供专用类型。对于 sqlite3 数据库,您需要使用类型转换器 将这些值存储为 sqlite3 支持的类型。虽然类型转换器在这里也可以使用,但它们会指示 Drift 在后台使用常规文本列。例如,当数据库内置对 UUID 的支持时,这可能会导致语句效率降低,或与其他与同一数据库通信的应用程序出现问题。因此,Drift 允许使用“自定义类型”——未在核心drift包中定义且并非适用于所有数据库的类型。
何时使用自定义类型 - 摘要
当将漂移支持扩展到具有尚未被漂移覆盖的自身类型的新数据库引擎时,自定义类型是一个很好的工具。
除非您要扩展 Drift 以使用新的数据库包(这很棒,请联系我们!),否则您可能不需要自己实现自定义类型。像 Drift 这样的包drift_postgres已经为您定义了相关的自定义类型。
定义类型
举个例子,假设我们有一个数据库,它原生支持Duration 通过interval类型传递值。我们使用的数据库驱动程序也原生支持Duration值,这意味着值可以通过预处理语句传递给数据库,也可以从行中读取,而无需手动转换。
Duration在这种情况下,将添加一个自定义类型类来实现对漂移的支持:
import 'package:drift/drift.dart';class DurationType implements CustomSqlType<Duration> {const DurationType();String mapToSqlLiteral(Duration dartValue) {return "interval '${dartValue.inMicroseconds} microseconds'";}Object mapToSqlParameter(Duration dartValue) => dartValue;Duration read(Object fromSql) => fromSql as Duration;String sqlTypeName(GenerationContext context) => 'interval';
}
此类型定义以下内容:
- 当Duration值映射到 SQL 文字时(例如,因为它们在Constants 中使用),我们interval '123754
microseconds’以 SQL 的方式表示它们。 - 当一个Duration值映射到一个参数时,我们直接使用该值(因为我们假设它受底层数据库驱动程序支持)。
- 类似地,我们希望数据库驱动程序正确地将持续时间作为的实例返回 Duration,因此反过来read也只是转换值。
- CREATE TABLE在语句和强制类型转换中使用的名称是interval。
使用自定义类型
在 Dart 中
要在 Dart 表上定义自定义类型,请使用customType具有以下类型的列构建器方法:
import 'package:drift/drift.dart';
import 'type.dart';class PeriodicReminders extends Table {IntColumn get id => integer().autoIncrement()();Column<Duration> get frequency => customType(const DurationType()).clientDefault(() => Duration(minutes: 15))();TextColumn get reminder => text()();
}
如示例所示,其他列约束clientDefault仍然可以添加到自定义列中。如果需要,您甚至可以组合自定义列和类型转换器。
这足以使大多数查询正常工作,但在某些高级场景中,您可能需要提供更多信息才能使用自定义类型。例如,当手动构造一个Variable或一个Constant带有自定义类型的 a 时,必须将自定义类型作为第二个参数添加到构造函数中。这是因为与内置类型不同,drift 没有一个中央寄存器来描述如何处理自定义类型值。
在 SQL 中
在 SQL 中,Drift 的内联 Dart语法可用于定义自定义类型:
import 'type.dart';CREATE TABLE periodic_reminders (id INTEGER NOT NULL PRIMARY KEY,frequency `const DurationType()` NOT NULL,reminder TEXT NOT NULL
);
请注意,漂移文件中对自定义类型的支持目前有限。例如,CAST表达式中目前不支持自定义类型。如果您对自定义类型的高级分析支持感兴趣,请提交问题或参与讨论,并描述您的用例,谢谢!
方言意识
当为某些数据库管理系统仅支持的 SQL 类型定义自定义类型时,您的数据库将仅适用于这些数据库系统。例如,任何使用DurationType 上述定义的表都无法与 sqlite3 兼容,因为它使用的interval类型被 sqlite3 解释为整数——而interval xyz microsecondssqlite3 根本不支持该语法。
从 Drift 2.15 开始,可以根据所使用的方言定义不同行为的自定义类型。这可以用来为其他数据库系统构建 polyfill。首先,考虑一个将持续时间存储为整数的自定义类型,类似于类型转换器可能执行的操作:
class _FallbackDurationType implements CustomSqlType<Duration> {const _FallbackDurationType();String mapToSqlLiteral(Duration dartValue) {return dartValue.inMicroseconds.toString();}Object mapToSqlParameter(Duration dartValue) {return dartValue.inMicroseconds;}Duration read(Object fromSql) {return Duration(microseconds: fromSql as int);}String sqlTypeName(GenerationContext context) {return 'integer';}
}const durationType = DialectAwareSqlType<Duration>.via(fallback: _FallbackDurationType(),overrides: {SqlDialect.postgres: DurationType(),},
);
通过使用DialectAwareSqlType,您可以在 PostgreSQL 数据库上自动使用该interval类型,同时在 sqlite3 和其他数据库上回退到整数类型:
Column<Duration> get frequency => customType(durationType).clientDefault(() => Duration(minutes: 15))();
支持的 SQLite 扩展
在分析.drift文件时,生成器会考虑可能存在的 sqlite3 扩展。但是,生成器无法识别数据库正在使用的 sqlite3 库,因此它会默认使用未启用任何扩展的旧版 sqlite3 库,并做出悲观的假设。使用类似 的包时,您将获得启用了 json1 和 fts5 扩展的最新 sqlite3 版本。您可以使用构建选项sqlite3_flutter_libs将此信息告知生成器。
json1
要在漂移文件和编译查询中启用 json1 扩展,请修改 构建选项以包含 json1在该sqlite_module部分中。
SQLite 扩展程序不需要任何特殊表,并且适用于所有文本列。在漂移文件和编译查询中,json启用该扩展程序后,所有函数均可用。
由于 json 扩展是可选的,因此在 Dart 中启用它需要特殊的导入。 package:drift/extensions/json1.dart下面是一个在 Dart 中使用 json 函数的示例:
import 'package:drift/drift.dart';
import 'package:drift/extensions/json1.dart';class Contacts extends Table {IntColumn get id => integer().autoIncrement()();TextColumn get data => text()();
}(tables: [Contacts])
class Database extends _$Database {// constructor and schemaVersion omitted for brevityFuture<List<Contacts>> findContactsWithNumber(String number) {return (select(contacts)..where((row) {// assume the phone number is stored in a json key in the `data` columnfinal phoneNumber = row.data.jsonExtract<String, StringType>('phone_number');return phoneNumber.equals(number);})).get();}
}
您可以在sqlite.org上了解有关 json1 扩展的更多信息。
fts5
fts5 扩展提供了 SQLite 表中的全文搜索功能。要在漂移文件和编译查询中启用 fts5 扩展,请修改 构建选项以包含 fts5在该sqlite_module部分中。
就像您在使用 sqlite 时所期望的那样,您可以使用CREATE VIRTUAL TABLE语句在漂移文件中创建 fts5 表。
CREATE VIRTUAL TABLE email USING fts5(sender, title, body);
fts5 表上的查询按预期工作:
emailsWithFts5: SELECT * FROM email WHERE email MATCH 'fts5' ORDER BY rank;
fts5 中的bm25、highlight和snippet函数也可用于自定义查询。
在 Dart 中无法声明 fts5 表或在 fts5 表上进行查询。您可以在sqlite.org上了解有关 fts5 扩展的更多信息。
地缘垄断
Geopoly 模块是R-Tree扩展的替代接口,它使用GeoJSON表示法 ( RFC-7946 ) 来描述二维多边形。Geopoly 包含以下函数:检测一个多边形是否包含于另一个多边形内或与另一个多边形重叠;计算多边形的封闭面积;对多边形进行线性变换;将多边形渲染为SVG 格式;以及其他类似的操作。
要geopoly在漂移文件和编译查询中启用扩展,请修改 构建选项以包含 geopoly在该sqlite_module部分中。
使用此扩展创建虚拟表的示例:
create virtual table geo using geopoly(geoID, a, b);
SQLite 会接受附加列(如上例中的geoID、a、 )中的任何类型,因此会为这些列生成一个类型,这并不总是很方便。为了避免这种情况,您可以像以下示例一样添加类型: bdriftDriftAny
create virtual table geo using geopoly (geoID INTEGER not null,a INTEGER,b
);
这将为列类型添加提示,然后 Dart 代码将更方便使用
您可以在sqlite.org上了解有关 geopoly 扩展的更多信息。