mirror of https://github.com/AMT-Cheif/drift.git
Expand documentation, support DISTINCT selects (#205)
This commit is contained in:
parent
45a8d8fbb3
commit
cf671ac56b
|
@ -156,17 +156,43 @@ mixin QueryEngine on DatabaseConnectionUser {
|
||||||
TableInfo<Tbl, R> table) =>
|
TableInfo<Tbl, R> table) =>
|
||||||
UpdateStatement(_resolvedEngine, table);
|
UpdateStatement(_resolvedEngine, table);
|
||||||
|
|
||||||
/// Starts a query on the given table. Queries can be limited with an limit
|
/// Starts a query on the given table.
|
||||||
/// or a where clause and can either return a current snapshot or a continuous
|
///
|
||||||
/// stream of data
|
/// In moor, queries are commonly used as a builder by chaining calls on them
|
||||||
|
/// using the `..` syntax from Dart. For instance, to load the 10 oldest users
|
||||||
|
/// with an 'S' in their name, you could use:
|
||||||
|
/// ```dart
|
||||||
|
/// Future<List<User>> oldestUsers() {
|
||||||
|
/// return (
|
||||||
|
/// select(users)
|
||||||
|
/// ..where((u) => u.name.like('%S%'))
|
||||||
|
/// ..orderBy([(u) => OrderingTerm(
|
||||||
|
/// expression: u.id,
|
||||||
|
/// mode: OrderingMode.asc
|
||||||
|
/// )])
|
||||||
|
/// ..limit(10)
|
||||||
|
/// ).get();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The [distinct] parameter (defaults to false) can be used to remove
|
||||||
|
/// duplicate rows from the result set.
|
||||||
|
///
|
||||||
|
/// For more information on queries, see the
|
||||||
|
/// [documentation](https://moor.simonbinder.eu/docs/getting-started/writing_queries/).
|
||||||
@protected
|
@protected
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
SimpleSelectStatement<T, R> select<T extends Table, R extends DataClass>(
|
SimpleSelectStatement<T, R> select<T extends Table, R extends DataClass>(
|
||||||
TableInfo<T, R> table) {
|
TableInfo<T, R> table,
|
||||||
return SimpleSelectStatement<T, R>(_resolvedEngine, table);
|
{bool distinct = false}) {
|
||||||
|
return SimpleSelectStatement<T, R>(_resolvedEngine, table,
|
||||||
|
distinct: distinct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts a [DeleteStatement] that can be used to delete rows from a table.
|
/// Starts a [DeleteStatement] that can be used to delete rows from a table.
|
||||||
|
///
|
||||||
|
/// See the [documentation](https://moor.simonbinder.eu/docs/getting-started/writing_queries/#updates-and-deletes)
|
||||||
|
/// for more details and example on how delete statements work.
|
||||||
@protected
|
@protected
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
DeleteStatement<T, D> delete<T extends Table, D extends DataClass>(
|
DeleteStatement<T, D> delete<T extends Table, D extends DataClass>(
|
||||||
|
|
|
@ -161,6 +161,28 @@ class _MappedSelectable<S, T> extends Selectable<T> {
|
||||||
|
|
||||||
mixin SingleTableQueryMixin<T extends Table, D extends DataClass>
|
mixin SingleTableQueryMixin<T extends Table, D extends DataClass>
|
||||||
on Query<T, D> {
|
on Query<T, D> {
|
||||||
|
/// Makes this statement only include rows that match the [filter].
|
||||||
|
///
|
||||||
|
/// For instance, if you have a table users with an id column, you could
|
||||||
|
/// select a user with a specific id by using
|
||||||
|
/// ```dart
|
||||||
|
/// (select(users)..where((u) => u.id.equals(42))).watchSingle()
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Please note that this [where] call is different to [Iterable.where] and
|
||||||
|
/// [Stream.where] in the sense that [filter] will NOT be called for each
|
||||||
|
/// row. Instead, it will only be called once (with the underlying table as
|
||||||
|
/// parameter). The result [Expression] will be written as a SQL string and
|
||||||
|
/// sent to the underlying database engine. The filtering does not happen in
|
||||||
|
/// Dart.
|
||||||
|
/// If a where condition has already been set before, the resulting filter
|
||||||
|
/// will be the conjunction of both calls.
|
||||||
|
///
|
||||||
|
/// For more information, see:
|
||||||
|
/// - The docs on [expressions](https://moor.simonbinder.eu/docs/getting-started/expressions/),
|
||||||
|
/// which explains how to express most SQL expressions in Dart.
|
||||||
|
/// If you want to remove duplicate rows from a query, use the `distinct`
|
||||||
|
/// parameter on [QueryEngine.select].
|
||||||
void where(Expression<bool, BoolType> filter(T tbl)) {
|
void where(Expression<bool, BoolType> filter(T tbl)) {
|
||||||
final predicate = filter(table.asDslTable);
|
final predicate = filter(table.asDslTable);
|
||||||
|
|
||||||
|
@ -212,6 +234,7 @@ mixin SingleTableQueryMixin<T extends Table, D extends DataClass>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mixin to provide the high-level [limit] methods for users.
|
||||||
mixin LimitContainerMixin<T extends Table, D extends DataClass> on Query<T, D> {
|
mixin LimitContainerMixin<T extends Table, D extends DataClass> on Query<T, D> {
|
||||||
/// Limits the amount of rows returned by capping them at [limit]. If [offset]
|
/// Limits the amount of rows returned by capping them at [limit]. If [offset]
|
||||||
/// is provided as well, the first [offset] rows will be skipped and not
|
/// is provided as well, the first [offset] rows will be skipped and not
|
||||||
|
|
|
@ -19,10 +19,15 @@ typedef OrderingTerm OrderClauseGenerator<T>(T tbl);
|
||||||
class JoinedSelectStatement<FirstT extends Table, FirstD extends DataClass>
|
class JoinedSelectStatement<FirstT extends Table, FirstD extends DataClass>
|
||||||
extends Query<FirstT, FirstD>
|
extends Query<FirstT, FirstD>
|
||||||
with LimitContainerMixin, Selectable<TypedResult> {
|
with LimitContainerMixin, Selectable<TypedResult> {
|
||||||
|
/// Whether to generate a `SELECT DISTINCT` query that will remove duplicate
|
||||||
|
/// rows from the result set.
|
||||||
|
final bool distinct;
|
||||||
|
|
||||||
/// Used internally by moor, users should use [SimpleSelectStatement.join]
|
/// Used internally by moor, users should use [SimpleSelectStatement.join]
|
||||||
/// instead.
|
/// instead.
|
||||||
JoinedSelectStatement(
|
JoinedSelectStatement(
|
||||||
QueryEngine database, TableInfo<FirstT, FirstD> table, this._joins)
|
QueryEngine database, TableInfo<FirstT, FirstD> table, this._joins,
|
||||||
|
[this.distinct = false])
|
||||||
: super(database, table);
|
: super(database, table);
|
||||||
|
|
||||||
final List<Join> _joins;
|
final List<Join> _joins;
|
||||||
|
@ -38,7 +43,7 @@ class JoinedSelectStatement<FirstT extends Table, FirstD extends DataClass>
|
||||||
@override
|
@override
|
||||||
void writeStartPart(GenerationContext ctx) {
|
void writeStartPart(GenerationContext ctx) {
|
||||||
ctx.hasMultipleTables = true;
|
ctx.hasMultipleTables = true;
|
||||||
ctx.buffer.write('SELECT ');
|
ctx.buffer..write(_beginOfSelect(distinct))..write(' ');
|
||||||
|
|
||||||
var isFirst = true;
|
var isFirst = true;
|
||||||
for (var table in _tables) {
|
for (var table in _tables) {
|
||||||
|
@ -176,9 +181,14 @@ class JoinedSelectStatement<FirstT extends Table, FirstD extends DataClass>
|
||||||
class SimpleSelectStatement<T extends Table, D extends DataClass>
|
class SimpleSelectStatement<T extends Table, D extends DataClass>
|
||||||
extends Query<T, D>
|
extends Query<T, D>
|
||||||
with SingleTableQueryMixin<T, D>, LimitContainerMixin<T, D>, Selectable<D> {
|
with SingleTableQueryMixin<T, D>, LimitContainerMixin<T, D>, Selectable<D> {
|
||||||
|
/// Whether duplicate rows should be eliminated from the result (this is a
|
||||||
|
/// `SELECT DISTINCT` statement in sql). Defaults to false.
|
||||||
|
final bool distinct;
|
||||||
|
|
||||||
/// Used internally by moor, users will want to call [QueryEngine.select]
|
/// Used internally by moor, users will want to call [QueryEngine.select]
|
||||||
/// instead.
|
/// instead.
|
||||||
SimpleSelectStatement(QueryEngine database, TableInfo<T, D> table)
|
SimpleSelectStatement(QueryEngine database, TableInfo<T, D> table,
|
||||||
|
{this.distinct = false})
|
||||||
: super(database, table);
|
: super(database, table);
|
||||||
|
|
||||||
/// The tables this select statement reads from.
|
/// The tables this select statement reads from.
|
||||||
|
@ -187,7 +197,9 @@ class SimpleSelectStatement<T extends Table, D extends DataClass>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void writeStartPart(GenerationContext ctx) {
|
void writeStartPart(GenerationContext ctx) {
|
||||||
ctx.buffer.write('SELECT * FROM ${table.tableWithAlias}');
|
ctx.buffer
|
||||||
|
..write(_beginOfSelect(distinct))
|
||||||
|
..write(' * FROM ${table.tableWithAlias}');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -226,7 +238,7 @@ class SimpleSelectStatement<T extends Table, D extends DataClass>
|
||||||
/// - [DatabaseConnectionUser.alias], which can be used to build statements
|
/// - [DatabaseConnectionUser.alias], which can be used to build statements
|
||||||
/// that refer to the same table multiple times.
|
/// that refer to the same table multiple times.
|
||||||
JoinedSelectStatement join(List<Join> joins) {
|
JoinedSelectStatement join(List<Join> joins) {
|
||||||
final statement = JoinedSelectStatement(database, table, joins);
|
final statement = JoinedSelectStatement(database, table, joins, distinct);
|
||||||
|
|
||||||
if (whereExpr != null) {
|
if (whereExpr != null) {
|
||||||
statement.where(whereExpr.predicate);
|
statement.where(whereExpr.predicate);
|
||||||
|
@ -269,6 +281,10 @@ class SimpleSelectStatement<T extends Table, D extends DataClass>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _beginOfSelect(bool distinct) {
|
||||||
|
return distinct ? 'SELECT DISTINCT' : 'SELECT';
|
||||||
|
}
|
||||||
|
|
||||||
/// A select statement that is constructed with a raw sql prepared statement
|
/// A select statement that is constructed with a raw sql prepared statement
|
||||||
/// instead of the high-level moor api.
|
/// instead of the high-level moor api.
|
||||||
class CustomSelectStatement with Selectable<QueryRow> {
|
class CustomSelectStatement with Selectable<QueryRow> {
|
||||||
|
|
|
@ -48,7 +48,7 @@ void main() {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
final result = await db.select(todos).join([
|
final result = await db.select(todos, distinct: true).join([
|
||||||
leftOuterJoin(categories, categories.id.equalsExp(todos.category))
|
leftOuterJoin(categories, categories.id.equalsExp(todos.category))
|
||||||
]).get();
|
]).get();
|
||||||
|
|
||||||
|
@ -67,6 +67,8 @@ void main() {
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
row.readTable(categories), Category(id: 3, description: 'description'));
|
row.readTable(categories), Category(id: 3, description: 'description'));
|
||||||
|
|
||||||
|
verify(executor.runSelect(argThat(contains('DISTINCT')), any));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('reports null when no data is available', () async {
|
test('reports null when no data is available', () async {
|
||||||
|
|
|
@ -31,8 +31,9 @@ void main() {
|
||||||
|
|
||||||
group('SELECT statements are generated', () {
|
group('SELECT statements are generated', () {
|
||||||
test('for simple statements', () {
|
test('for simple statements', () {
|
||||||
db.select(db.users).get();
|
db.select(db.users, distinct: true).get();
|
||||||
verify(executor.runSelect('SELECT * FROM users;', argThat(isEmpty)));
|
verify(executor.runSelect(
|
||||||
|
'SELECT DISTINCT * FROM users;', argThat(isEmpty)));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('with limit statements', () {
|
test('with limit statements', () {
|
||||||
|
|
Loading…
Reference in New Issue