Skip to content

运行时模式自省(Runtime schema introspection)

摘要

使用生成的表类来反射性地检查数据库的模式。


由于 drift 生成的类型安全的表类,在 Dart 中编写 SQL 查询变得简单而安全。但是,这些查询通常是针对特定表编写的。虽然 drift 支持表的继承,但有时以反射方式访问表会更容易。幸运的是,drift 生成的代码实现了可用于执行此操作的接口。

由于这是大多数 drift 用户不需要的主题,因此本页主要提供激励性示例和指向相关 drift 类文档的链接。例如,你可能有多个独立的表,它们都有一个 id 列。你可能希望按其 id 列过滤行。当针对单个表(如入门页面中看到的 Todos 表)编写此查询时,这非常简单:

但是,假设我们想将此查询推广到每个数据库表,那会是什么样子?以下代码段显示了如何做到这一点(请注意,代码段中的链接直接指向相关文档):

由于这比只适用于单个表的查询复杂得多,让我们详细看一下每个有趣的行:

  • FindByIdResultSetImplementation 上的一个扩展。此类是 drift 生成的每个表或视图的超类。它定义了用于检查模式或将表示数据库行的原始 Map 转换为生成的数据类的有用方法。
    • ResultSetImplementation 用两个类型参数实例化:原始表类和生成的行类。例如,如果你定义一个表 class Todos extends Table,drift 将生成一个扩展 Todos 的类,同时还实现 ResultSetImplementation<Todos, Todo>(其中 Todo 是生成的数据类)。
    • ResultSetImplementation 有两个子类:TableInfoViewInfo,它们分别混入到生成的表和视图类中。
    • HasResultSetTableView 的超类,这两个类用于在 drift 中声明表和视图。
  • Selectable<Row> 表示一个查询,你可以在其上使用 get()watch()getSingle()watchSingle() 等方法来运行查询。
  • findById 中使用的 select() 扩展可用于在没有对数据库类的引用的情况下启动 select 语句 - 你只需要表实例。
  • 我们可以使用 columnsByName 按其在 SQL 中的名称查找列。在这里,我们期望存在一个 int 列。
  • GeneratedColumn 类表示数据库中的一列。可以从列实例中读取列约束、类型或默认值等内容。
    • 特别是,我们使用它来断言表确实有一个名为 idIntColumn

要调用此扩展,可以使用 await myDatabase.todos.findById(3).getSingle()。将方法定义为扩展的一个好处是类型推断效果很好 - 在 todos 上调用 findById 会返回一个 Todo 实例,即此表的生成数据类。

更新和插入

同样的方法也适用于构造 update、delete 和 insert 语句(尽管这些需要一个 TableInfo 而不是 ResultSetImplementation,因为视图是只读的)。此外,更新和插入使用一个 Insertable 对象,该对象分别表示已更新或已插入列的部分行。对于已知的表,可以使用生成的类型化的 Companion 对象。但是,由于 RawValuesInsertable 的存在,这也可以通过模式自省来完成,RawValuesInsertable 可以用作由列名到值的映射支持的通用 Insertable

此示例建立在前一个示例的基础上,根据 id 列的过滤器更新通用表的 title 列:

dart
extension UpdateTitle on DatabaseConnectionUser {
  Future<Row?> updateTitle<T extends TableInfo<Table, Row>, Row>(
    T table,
    int id,
    String newTitle,
  ) async {
    final columnsByName = table.columnsByName;
    final stmt = update(table)
      ..where((tbl) {
        final idColumn = columnsByName['id'];

        if (idColumn == null) {
          throw ArgumentError.value(
            this,
            'this',
            'Must be a table with an id column',
          );
        }

        if (idColumn.type != DriftSqlType.int) {
          throw ArgumentError('Column `id` is not an integer');
        }

        return idColumn.equals(id);
      });

    final rows = await stmt.writeReturning(
      RawValuesInsertable({'title': Variable<String>(newTitle)}),
    );

    return rows.singleOrNull;
  }
}

在数据库或数据库访问器类中,可以像这样调用该方法:

dart
Future<Todo?> updateTodoTitle(int id, String newTitle) {
  return updateTitle(todos, id, newTitle);
}

希望本页能为你提供一些开始反射性地检查你的 drift 数据库的指针。链接的 Dart 文档也更详细地解释了这些概念。如果你对此有疑问,或者对本页包含更多示例有建议,请随时开始讨论