mirror of https://github.com/AMT-Cheif/drift.git
Add docs on schema reflection
This commit is contained in:
parent
99884c55ca
commit
039ff942f7
|
@ -0,0 +1,36 @@
|
|||
import 'package:drift/drift.dart';
|
||||
|
||||
import 'drift/example.drift.dart';
|
||||
|
||||
// #docregion findById
|
||||
extension FindById<Table extends HasResultSet, Row>
|
||||
on ResultSetImplementation<Table, Row> {
|
||||
Selectable<Row> findById(int id) {
|
||||
return select()
|
||||
..where((row) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
// #enddocregion findById
|
||||
|
||||
extension FindTodoEntryById on GeneratedDatabase {
|
||||
Todos get todos => Todos(this);
|
||||
|
||||
// #docregion findTodoEntryById
|
||||
Selectable<Todo> findTodoEntryById(int id) {
|
||||
return select(todos)..where((row) => row.id.equals(id));
|
||||
}
|
||||
// #enddocregion findTodoEntryById
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
data:
|
||||
title: Runtime schema inspection
|
||||
description: Use generated table classes to reflectively inspect the schema of your database.
|
||||
template: layouts/docs/single
|
||||
---
|
||||
|
||||
{% assign snippets = 'package:drift_docs/snippets/modular/schema_inspection.dart.excerpt.json' | readString | json_decode %}
|
||||
|
||||
Thanks to the typesafe table classes generated by drift, [writing SQL queries]({{ '../Getting started/writing_queries.md' | pageUrl }}) in Dart
|
||||
is simple and safe.
|
||||
However, these queries are usually written against a specific table. And while drift supports inheritance for tables, sometimes it is easier
|
||||
to access tables reflectively. Luckily, code generated by drift implements interfaces which can be used to do just that.
|
||||
|
||||
Since this is a topic that most drift users will not need, this page mostly gives motivating examples and links to the documentation for relevant
|
||||
drift classes.
|
||||
For instance, you might have multiple independent tables that have an `id` column. And you might want to filter rows by their `id` column.
|
||||
When writing this query against a single table, like the `Todos` table as seen in the [getting started]({{'../Getting started/index.md' | pageUrl }}) page,
|
||||
that's pretty straightforward:
|
||||
|
||||
{% include "blocks/snippet" snippets = snippets name = 'findTodoEntryById' %}
|
||||
|
||||
But let's say we want to generalize this query to every database table, how could that look like?
|
||||
This following snippet shows how this can be done (note that the links in the snippet point directly towards the relevant documentation):
|
||||
|
||||
{% include "blocks/snippet" snippets = snippets name = 'findById' %}
|
||||
|
||||
Since that is much more complicated than the query that only works for a single table, let's take a look at each interesting line in detail:
|
||||
|
||||
- `FindById` is an extension on [ResultSetImplementation]. This class is the superclass for every table or view generated by drift.
|
||||
It defines useful methods to inspect the schema, or to translate a raw `Map` representing a database row into the generated data class.
|
||||
- `ResultSetImplementation` is instantiated with two type arguments: The original table class and the generated row class.
|
||||
For instance, if you define a table `class Todos extends Table`, drift would generate a class that extends `Todos` while also implementing.
|
||||
`ResultSetImplementation<Todos, Todo>` (with `Todo` being the generated data class).
|
||||
- `ResultSetImplementation` has two subclasses: [TableInfo] and [ViewInfo] which are mixed in to generated table and view classes, respectively.
|
||||
- `HasResultSet` is the superclass for `Table` and `View`, the two classes used to declare tables and views in drift.
|
||||
- `Selectable<Row>` represents a query, you can use methods like `get()`, `watch()`, `getSingle()` and `watchSingle()` on it to run the query.
|
||||
- The `select()` extension used in `findById` can be used to start a select statement without a reference to a database class - all you need is
|
||||
the table instance.
|
||||
- We can use `columnsByName` to find a column by its name in SQL. Here, we expect an `int` column to exist.
|
||||
- The [GeneratedColumn] class represents a column in a database. Things like column constraints, the type or default values can be read from the
|
||||
column instance.
|
||||
- In particular, we use this to assert that the table indeed has an `IntColumn` named `id`.
|
||||
|
||||
To call this extension, `await myDatabase.todos.findById(3).getSingle()` could be used.
|
||||
A nice thing about defining the method as an extension is that type inference works really well - calling `findById` on `todos`
|
||||
returns a `Todo` instance, the generated data class for this table.
|
||||
|
||||
The same approach also works to construct update, delete and insert statements (although those require a [TableInfo] instead of a [ResultSetImplementation]
|
||||
as views are read-only).
|
||||
|
||||
Hopefully, this page gives you some pointers to start reflectively inspecting your drift databases.
|
||||
The linked Dart documentation also expains the concepts in more detail.
|
||||
If you have questions about this, or have a suggestion for more examples to include on this page, feel free to [start a discussion](https://github.com/simolus3/drift/discussions/new?category=q-a) about this.
|
||||
|
||||
[ResultSetImplementation]: https://drift.simonbinder.eu/api/drift/resultsetimplementation-class
|
||||
[TableInfo]: https://drift.simonbinder.eu/api/drift/tableinfo-mixin
|
||||
[ViewInfo]: https://drift.simonbinder.eu/api/drift/viewinfo-class
|
||||
[GeneratedColumn]: https://drift.simonbinder.eu/api/drift/generatedcolumn-class
|
|
@ -14,10 +14,7 @@ data:
|
|||
Write type-safe queries in Dart or SQL, enjoy auto-updating streams, easily managed transactions
|
||||
and so much more to make persistence fun.
|
||||
</p>
|
||||
<a class="btn btn-lg btn-primary mr-3 mb-4" href="{{ 'docs/index' | pageUrl }}">
|
||||
Learn more <i class="fas fa-arrow-alt-circle-right ml-2"></i>
|
||||
</a>
|
||||
<a class="btn btn-lg btn-secondary mr-3 mb-4" href="https://pub.dev/packages/drift">
|
||||
<a class="btn btn-lg btn-secondary mr-3 mb-4" href="{{ 'docs/Getting started/index.md' | pageUrl }}">
|
||||
Get started <i class="fas fa-code ml-2 "></i>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -11,7 +11,7 @@ class Users extends Table {
|
|||
|
||||
@DriftDatabase(tables: [Users])
|
||||
class Database extends _$Database {
|
||||
Database.connect(DatabaseConnection c) : super(c);
|
||||
Database(QueryExecutor c) : super(c);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:drift_docs/snippets/migrations/datetime_conversion.dart';
|
||||
import 'package:drift_docs/snippets/modular/schema_inspection.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'generated/database.dart';
|
||||
|
@ -11,7 +12,7 @@ import 'generated/database.dart';
|
|||
void main() {
|
||||
group('changing datetime format', () {
|
||||
test('unix timestamp to text', () async {
|
||||
final db = Database.connect(DatabaseConnection(NativeDatabase.memory()));
|
||||
final db = Database(DatabaseConnection(NativeDatabase.memory()));
|
||||
addTearDown(db.close);
|
||||
|
||||
final time = DateTime.fromMillisecondsSinceEpoch(
|
||||
|
@ -42,7 +43,7 @@ void main() {
|
|||
|
||||
test('text to unix timestamp', () async {
|
||||
// First, create all tables using text as datetime
|
||||
final db = Database.connect(DatabaseConnection(NativeDatabase.memory()));
|
||||
final db = Database(DatabaseConnection(NativeDatabase.memory()));
|
||||
db.options = const DriftDatabaseOptions(storeDateTimeAsText: true);
|
||||
addTearDown(db.close);
|
||||
|
||||
|
@ -72,7 +73,7 @@ void main() {
|
|||
|
||||
test('text to unix timestamp, support old sqlite', () async {
|
||||
// First, create all tables using text as datetime
|
||||
final db = Database.connect(DatabaseConnection(NativeDatabase.memory()));
|
||||
final db = Database(DatabaseConnection(NativeDatabase.memory()));
|
||||
db.options = const DriftDatabaseOptions(storeDateTimeAsText: true);
|
||||
addTearDown(db.close);
|
||||
|
||||
|
@ -100,4 +101,20 @@ void main() {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('runtime schema inspection', () {
|
||||
test('findById', () async {
|
||||
final db = Database(NativeDatabase.memory());
|
||||
addTearDown(db.close);
|
||||
|
||||
await db.batch((batch) {
|
||||
batch.insert(db.users, UsersCompanion.insert(name: 'foo')); // 1
|
||||
batch.insert(db.users, UsersCompanion.insert(name: 'bar')); // 2
|
||||
batch.insert(db.users, UsersCompanion.insert(name: 'baz')); // 3
|
||||
});
|
||||
|
||||
final row = await db.users.findById(2).getSingle();
|
||||
expect(row.name, 'bar');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -99,6 +99,10 @@ abstract class ResultSetImplementation<Tbl, Row> extends DatabaseSchemaEntity {
|
|||
/// when used in a query.
|
||||
ResultSetImplementation<Tbl, Row> createAlias(String alias) =>
|
||||
_AliasResultSet(alias, this);
|
||||
|
||||
/// Gets all [$columns] in this table or view, indexed by their (non-escaped)
|
||||
/// name.
|
||||
Map<String, GeneratedColumn> get columnsByName;
|
||||
}
|
||||
|
||||
class _AliasResultSet<Tbl, Row> extends ResultSetImplementation<Tbl, Row> {
|
||||
|
@ -131,6 +135,10 @@ class _AliasResultSet<Tbl, Row> extends ResultSetImplementation<Tbl, Row> {
|
|||
|
||||
@override
|
||||
Tbl get asDslTable => _inner.asDslTable;
|
||||
|
||||
@override
|
||||
Map<String, GeneratedColumn<Object>> get columnsByName =>
|
||||
_inner.columnsByName;
|
||||
}
|
||||
|
||||
/// Extension to generate an alias for a table or a view.
|
||||
|
|
|
@ -48,7 +48,7 @@ mixin TableInfo<TableDsl extends Table, D> on Table
|
|||
|
||||
Map<String, GeneratedColumn>? _columnsByName;
|
||||
|
||||
/// Gets all [$columns] in this table, indexed by their (non-escaped) name.
|
||||
@override
|
||||
Map<String, GeneratedColumn> get columnsByName {
|
||||
return _columnsByName ??= {
|
||||
for (final column in $columns) column.$name: column
|
||||
|
|
|
@ -29,4 +29,13 @@ abstract class ViewInfo<Self extends HasResultSet, Row>
|
|||
/// If this view reads from other views, the [readTables] of that view are
|
||||
/// also included in this [readTables] set.
|
||||
Set<String> get readTables;
|
||||
|
||||
Map<String, GeneratedColumn>? _columnsByName;
|
||||
|
||||
@override
|
||||
Map<String, GeneratedColumn> get columnsByName {
|
||||
return _columnsByName ??= {
|
||||
for (final column in $columns) column.$name: column
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue