platform/angel_orm/lib/src/query.dart

307 lines
7.9 KiB
Dart
Raw Normal View History

2018-12-01 17:21:34 +00:00
import 'dart:async';
2018-12-01 18:23:50 +00:00
import 'annotations.dart';
2018-12-01 17:21:34 +00:00
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-12-01 22:39:10 +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;
2018-12-01 19:12:07 +00:00
String compile({bool includeTableName: false, String preamble});
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();
2018-12-01 22:39:10 +00:00
return executor
.query(sql, fields)
.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 18:23:50 +00:00
class OrderBy {
final String key;
final bool descending;
const OrderBy(this.key, {this.descending: false});
String compile() => descending ? '$key DESC' : '$key ASC';
}
2018-12-01 17:21:34 +00:00
/// A SQL `SELECT` query builder.
abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
2018-12-01 18:23:50 +00:00
final List<OrderBy> _orderBy = [];
String _crossJoin, _groupBy;
int _limit, _offset;
2018-12-01 18:27:42 +00:00
JoinBuilder _join;
2018-12-01 18:23:50 +00:00
2018-12-01 17:21:34 +00:00
/// 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
/// A reference to an abstract query builder.
///
/// This is often a generated class.
Where get where;
2018-08-24 12:30:38 +00:00
2018-12-01 19:12:07 +00:00
/// Makes a new [Where] clause.
Where newWhereClause() {
throw new UnsupportedError(
'This instance does not support creating new WHERE clauses.');
}
/// Shorthand for calling [where].or with a new [Where] clause.
void andWhere(void Function(Where) f) {
var w = newWhereClause();
f(w);
where.and(w);
}
/// Shorthand for calling [where].or with a new [Where] clause.
void notWhere(void Function(Where) f) {
var w = newWhereClause();
f(w);
where.not(w);
}
/// Shorthand for calling [where].or with a new [Where] clause.
void orWhere(void Function(Where) f) {
var w = newWhereClause();
f(w);
where.or(w);
}
2018-12-01 18:23:50 +00:00
/// Limit the number of rows to return.
void limit(int n) {
_limit = n;
}
/// Skip a number of rows in the query.
void offset(int n) {
_offset = n;
}
/// Groups the results by a given key.
void groupBy(String key) {
_groupBy = key;
}
/// Sorts the results by a key.
void orderBy(String key, {bool descending: false}) {
_orderBy.add(new OrderBy(key, descending: descending));
}
/// Execute a `CROSS JOIN` (Cartesian product) against another table.
void crossJoin(String tableName) {
_crossJoin = tableName;
}
/// Execute an `INNER JOIN` against another table.
void join(String tableName, String localKey, String foreignKey,
{String op: '='}) {
2018-12-01 19:12:07 +00:00
_join = new JoinBuilder(
JoinType.inner, this, tableName, localKey, foreignKey,
op: op);
2018-12-01 18:23:50 +00:00
}
/// Execute a `LEFT JOIN` against another table.
void leftJoin(String tableName, String localKey, String foreignKey,
{String op: '='}) {
2018-12-01 19:12:07 +00:00
_join = new JoinBuilder(
JoinType.left, this, tableName, localKey, foreignKey,
op: op);
2018-12-01 18:23:50 +00:00
}
/// Execute a `RIGHT JOIN` against another table.
void rightJoin(String tableName, String localKey, String foreignKey,
{String op: '='}) {
2018-12-01 19:12:07 +00:00
_join = new JoinBuilder(
JoinType.right, this, tableName, localKey, foreignKey,
op: op);
2018-12-01 18:23:50 +00:00
}
/// Execute a `FULL OUTER JOIN` against another table.
void fullOuterJoin(String tableName, String localKey, String foreignKey,
{String op: '='}) {
2018-12-01 19:12:07 +00:00
_join = new JoinBuilder(
JoinType.full, this, tableName, localKey, foreignKey,
op: op);
2018-12-01 18:23:50 +00:00
}
/// Execute a `SELF JOIN`.
void selfJoin(String tableName, String localKey, String foreignKey,
{String op: '='}) {
2018-12-01 19:12:07 +00:00
_join = new JoinBuilder(
JoinType.self, this, tableName, localKey, foreignKey,
op: op);
2018-12-01 18:23:50 +00:00
}
2018-08-24 12:30:38 +00:00
@override
2018-12-01 19:12:07 +00:00
String compile({bool includeTableName: false, String preamble}) {
var b = new StringBuffer(preamble ?? 'SELECT ');
2018-12-01 18:23:50 +00:00
var f = fields ?? ['*'];
if (includeTableName) f = f.map((s) => '$tableName.$s').toList();
b.write(f.join(', '));
2018-12-01 17:21:34 +00:00
b.write(' FROM $tableName');
2018-12-01 18:23:50 +00:00
var whereClause =
where.compile(tableName: includeTableName ? tableName : null);
2018-12-01 17:21:34 +00:00
if (whereClause.isNotEmpty) b.write(' WHERE $whereClause');
2018-12-01 18:23:50 +00:00
if (_limit != null) b.write(' LIMIT $_limit');
if (_offset != null) b.write(' OFFSET $_offset');
if (_groupBy != null) b.write(' GROUP BY $_groupBy');
for (var item in _orderBy) b.write(' ${item.compile()}');
if (_crossJoin != null) b.write(' CROSS JOIN $_crossJoin');
if (_join != null) b.write(' ${_join.compile()}');
2018-12-01 17:21:34 +00:00
return b.toString();
2018-08-24 12:30:38 +00:00
}
2018-12-01 22:39:10 +00:00
Future<List<T>> delete(QueryExecutor executor) async {
var sql = compile(preamble: 'DELETE FROM $tableName');
return executor
.query(sql, fields)
.then((it) => it.map(deserialize).toList());
}
Future<T> deleteOne(QueryExecutor executor) {
return delete(executor).then((it) => it.isEmpty ? null : it.first);
}
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();
2018-12-01 18:23:50 +00:00
final Set<QueryWhere> _not = new Set();
2018-12-01 17:21:34 +00:00
final Set<QueryWhere> _or = new Set();
2018-08-24 12:30:38 +00:00
2018-12-01 18:23:50 +00:00
Iterable<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 19:12:07 +00:00
void not(QueryWhere other) {
_not.add(other);
}
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 18:23:50 +00:00
String compile({String tableName}) {
2018-12-01 17:21:34 +00:00
var b = new StringBuffer();
int i = 0;
2018-08-24 12:30:38 +00:00
2018-12-01 18:23:50 +00:00
for (var builder in expressionBuilders) {
var key = builder.columnName;
if (tableName != null) key = '$tableName.$key';
2018-12-01 17:21:34 +00:00
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 18:23:50 +00:00
for (var other in _not) {
var sql = other.compile();
if (sql.isNotEmpty) b.write(' NOT $sql');
}
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
2018-12-01 22:39:10 +00:00
@override
List<String> get fields => left.fields;
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 19:12:07 +00:00
String compile({bool includeTableName: false, String preamble}) {
2018-12-01 17:21:34 +00:00
var selector = all == true ? 'UNION ALL' : 'UNION';
2018-12-01 18:23:50 +00:00
return '(${left.compile(includeTableName: includeTableName)}) $selector (${right.compile(includeTableName: includeTableName)})';
}
}
/// Builds a SQL `JOIN` query.
2018-12-01 18:27:42 +00:00
class JoinBuilder {
2018-12-01 18:23:50 +00:00
final JoinType type;
final Query from;
final String to, key, value, op;
2018-12-01 19:12:07 +00:00
JoinBuilder(this.type, this.from, this.to, this.key, this.value,
{this.op: '='});
2018-12-01 18:23:50 +00:00
String compile() {
var b = new StringBuffer();
var left = '${from.tableName}.$key';
var right = '$to.$value';
switch (type) {
case JoinType.inner:
b.write(' INNER JOIN');
break;
case JoinType.left:
b.write(' LEFT JOIN');
break;
case JoinType.right:
b.write(' RIGHT JOIN');
break;
case JoinType.full:
b.write(' FULL OUTER JOIN');
break;
case JoinType.self:
b.write(' SELF JOIN');
break;
}
b.write(' $to ON $left$op$right');
return b.toString();
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 18:23:50 +00:00
class JoinOn {
final SqlExpressionBuilder key;
final SqlExpressionBuilder value;
JoinOn(this.key, this.value);
}
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 22:39:10 +00:00
Future<List<List>> query(String query, List<String> returningFields);
2018-08-24 12:30:38 +00:00
}