platform/angel_orm/lib/src/query.dart

123 lines
2.9 KiB
Dart
Raw Normal View History

2018-12-01 17:21:34 +00:00
import 'dart:async';
import 'builder.dart';
2017-06-18 04:19:05 +00:00
2018-12-01 17:21:34 +00:00
/// A base class for objects that compile to SQL queries, typically within an ORM.
abstract class QueryBase<T> {
2018-08-24 12:30:38 +00:00
String compile();
2017-06-18 04:19:05 +00:00
2018-12-01 17:21:34 +00:00
T deserialize(List row);
2017-06-18 04:19:05 +00:00
2018-12-01 17:21:34 +00:00
Future<List<T>> get(QueryExecutor executor) async {
var sql = compile();
return executor.query(sql).then((it) => it.map(deserialize).toList());
2018-08-24 12:30:38 +00:00
}
2017-07-14 22:04:58 +00:00
2018-12-01 17:21:34 +00:00
Future<T> getOne(QueryExecutor executor) {
return get(executor).then((it) => it.isEmpty ? null : it.first);
2018-08-24 12:30:38 +00:00
}
2018-12-01 17:21:34 +00:00
Union<T> union(QueryBase<T> other) {
return new Union(this, other);
2018-08-24 12:30:38 +00:00
}
2017-06-18 04:19:05 +00:00
2018-12-01 17:21:34 +00:00
Union<T> unionAll(QueryBase<T> other) {
return new Union(this, other, all: true);
2017-07-14 22:04:58 +00:00
}
2017-06-18 04:19:05 +00:00
}
2018-12-01 17:21:34 +00:00
/// A SQL `SELECT` query builder.
abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
/// The table against which to execute this query.
String get tableName;
2017-07-14 22:04:58 +00:00
2018-12-01 17:21:34 +00:00
/// The list of fields returned by this query.
///
/// If it's `null`, then this query will perform a `SELECT *`.
List<String> get fields;
2017-07-14 22:04:58 +00:00
2018-12-01 17:21:34 +00:00
/// A reference to an abstract query builder.
///
/// This is often a generated class.
Where get where;
2018-08-24 12:30:38 +00:00
@override
String compile() {
2018-12-01 17:21:34 +00:00
var b = new StringBuffer('SELECT ');
if (fields == null)
b.write('*');
else
b.write(fields.join(', '));
b.write(' FROM $tableName');
var whereClause = where.compile();
if (whereClause.isNotEmpty) b.write(' WHERE $whereClause');
return b.toString();
2018-08-24 12:30:38 +00:00
}
2017-06-18 04:19:05 +00:00
}
2018-12-01 17:21:34 +00:00
/// Builds a SQL `WHERE` clause.
abstract class QueryWhere {
final Set<QueryWhere> _and = new Set();
final Set<QueryWhere> _or = new Set();
2018-08-24 12:30:38 +00:00
2018-12-01 17:21:34 +00:00
Map<String, SqlExpressionBuilder> get expressionBuilders;
2018-08-24 12:30:38 +00:00
2018-12-01 17:21:34 +00:00
void and(QueryWhere other) {
_and.add(other);
2018-08-24 12:30:38 +00:00
}
2018-12-01 17:21:34 +00:00
void or(QueryWhere other) {
_or.add(other);
2018-08-24 12:30:38 +00:00
}
2018-12-01 17:21:34 +00:00
String compile() {
var b = new StringBuffer();
int i = 0;
2018-08-24 12:30:38 +00:00
2018-12-01 17:21:34 +00:00
for (var entry in expressionBuilders.entries) {
var key = entry.key, builder = entry.value;
if (builder.hasValue) {
if (i++ > 0) b.write(' AND ');
b.write('$key ${builder.compile()}');
}
}
2018-08-24 12:30:38 +00:00
2018-12-01 17:21:34 +00:00
for (var other in _and) {
var sql = other.compile();
if (sql.isNotEmpty) b.write(' AND $sql');
}
2018-08-24 12:30:38 +00:00
2018-12-01 17:21:34 +00:00
for (var other in _or) {
var sql = other.compile();
if (sql.isNotEmpty) b.write(' OR $sql');
}
2018-08-24 12:30:38 +00:00
2018-12-01 17:21:34 +00:00
return b.toString();
2018-08-24 12:30:38 +00:00
}
2017-06-18 04:19:05 +00:00
}
2018-12-01 17:21:34 +00:00
/// Represents the `UNION` of two subqueries.
class Union<T> extends QueryBase<T> {
final QueryBase<T> left, right;
final bool all;
2018-08-24 12:30:38 +00:00
2018-12-01 17:21:34 +00:00
Union(this.left, this.right, {this.all: false});
2018-08-24 12:30:38 +00:00
@override
2018-12-01 17:21:34 +00:00
T deserialize(List row) => left.deserialize(row);
2018-08-24 12:30:38 +00:00
@override
2018-12-01 17:21:34 +00:00
String compile() {
var selector = all == true ? 'UNION ALL' : 'UNION';
return '(${left.compile()}) $selector (${right.compile()})';
2018-08-24 12:30:38 +00:00
}
2018-12-01 17:21:34 +00:00
}
2018-08-24 12:30:38 +00:00
2018-12-01 17:21:34 +00:00
/// An abstract interface that performs queries.
///
/// This class should be implemented.
abstract class QueryExecutor {
const QueryExecutor();
2018-08-24 12:30:38 +00:00
2018-12-01 17:21:34 +00:00
Future<List<List>> query(String query);
2018-08-24 12:30:38 +00:00
}