2018-12-01 17:21:34 +00:00
|
|
|
import 'dart:async';
|
2018-12-03 16:50:43 +00:00
|
|
|
import 'package:charcode/ascii.dart';
|
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-03 16:50:43 +00:00
|
|
|
bool isAscii(int ch) => ch >= $nul && ch <= $del;
|
|
|
|
|
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-03 13:41:14 +00:00
|
|
|
/// A String of all [fields], joined by a comma (`,`).
|
|
|
|
String get fieldSet => fields.join(', ');
|
|
|
|
|
2018-12-03 23:13:11 +00:00
|
|
|
String compile(
|
|
|
|
{bool includeTableName: false, String preamble, bool withFields: true});
|
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-03 16:50:43 +00:00
|
|
|
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 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-08 02:03:03 +00:00
|
|
|
String toSql(Object obj, {bool withQuotes: true}) {
|
2018-12-03 13:41:14 +00:00
|
|
|
if (obj is DateTime) {
|
2018-12-08 02:03:03 +00:00
|
|
|
return withQuotes ? "'${dateYmdHms.format(obj)}'" : dateYmdHms.format(obj);
|
2018-12-03 13:41:14 +00:00
|
|
|
} else if (obj is bool) {
|
|
|
|
return obj ? 'TRUE' : 'FALSE';
|
|
|
|
} else if (obj == null) {
|
|
|
|
return 'NULL';
|
|
|
|
} else if (obj is String) {
|
2018-12-03 16:50:43 +00:00
|
|
|
var b = new StringBuffer();
|
2018-12-08 02:03:03 +00:00
|
|
|
var escaped = false;
|
2018-12-03 16:50:43 +00:00
|
|
|
var it = obj.runes.iterator;
|
|
|
|
|
|
|
|
while (it.moveNext()) {
|
|
|
|
if (it.current == $nul)
|
|
|
|
continue; // Skip null byte
|
2018-12-08 02:03:03 +00:00
|
|
|
else if (it.current == $single_quote) {
|
|
|
|
escaped = true;
|
|
|
|
b.write('\\x');
|
|
|
|
b.write(it.current.toRadixString(16).padLeft(2, '0'));
|
|
|
|
} else if (isAscii(it.current)) {
|
2018-12-03 16:50:43 +00:00
|
|
|
b.writeCharCode(it.current);
|
|
|
|
} else if (it.currentSize == 1) {
|
2018-12-08 02:03:03 +00:00
|
|
|
escaped = true;
|
2018-12-03 16:50:43 +00:00
|
|
|
b.write('\\u');
|
|
|
|
b.write(it.current.toRadixString(16).padLeft(4, '0'));
|
|
|
|
} else if (it.currentSize == 2) {
|
2018-12-08 02:03:03 +00:00
|
|
|
escaped = true;
|
2018-12-03 16:50:43 +00:00
|
|
|
b.write('\\U');
|
|
|
|
b.write(it.current.toRadixString(16).padLeft(8, '0'));
|
|
|
|
} else {
|
|
|
|
throw new UnsupportedError(
|
|
|
|
'toSql() cannot encode a rune of size (${it.currentSize})');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-08 02:03:03 +00:00
|
|
|
if (!withQuotes)
|
|
|
|
return b.toString();
|
|
|
|
else if (escaped)
|
|
|
|
return "E'$b'";
|
|
|
|
else
|
|
|
|
return "'$b'";
|
2018-12-03 13:41:14 +00:00
|
|
|
} else {
|
|
|
|
return obj.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-03 23:13:11 +00:00
|
|
|
final List<JoinBuilder> _joins = [];
|
2018-12-01 18:23:50 +00:00
|
|
|
final List<OrderBy> _orderBy = [];
|
|
|
|
String _crossJoin, _groupBy;
|
|
|
|
int _limit, _offset;
|
|
|
|
|
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.
|
|
|
|
///
|
2018-12-03 13:41:14 +00:00
|
|
|
/// This is usually a generated class.
|
2018-12-01 17:21:34 +00:00
|
|
|
Where get where;
|
2018-08-24 12:30:38 +00:00
|
|
|
|
2018-12-03 13:41:14 +00:00
|
|
|
/// A set of values, for an insertion or update.
|
|
|
|
///
|
|
|
|
/// This is usually a generated class.
|
|
|
|
QueryValues get values;
|
|
|
|
|
2018-12-08 02:03:03 +00:00
|
|
|
String adornWithTableName(String s) => '$tableName.$s';
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-12-03 23:13:11 +00:00
|
|
|
String _joinAlias() => 'a${_joins.length}';
|
|
|
|
|
2018-12-01 18:23:50 +00:00
|
|
|
/// Execute an `INNER JOIN` against another table.
|
|
|
|
void join(String tableName, String localKey, String foreignKey,
|
2018-12-03 23:13:11 +00:00
|
|
|
{String op: '=', List<String> additionalFields: const []}) {
|
|
|
|
_joins.add(new JoinBuilder(
|
2018-12-01 19:12:07 +00:00
|
|
|
JoinType.inner, this, tableName, localKey, foreignKey,
|
2018-12-03 23:13:11 +00:00
|
|
|
op: op, alias: _joinAlias(), additionalFields: additionalFields));
|
2018-12-01 18:23:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Execute a `LEFT JOIN` against another table.
|
|
|
|
void leftJoin(String tableName, String localKey, String foreignKey,
|
2018-12-03 23:13:11 +00:00
|
|
|
{String op: '=', List<String> additionalFields: const []}) {
|
|
|
|
_joins.add(new JoinBuilder(
|
2018-12-01 19:12:07 +00:00
|
|
|
JoinType.left, this, tableName, localKey, foreignKey,
|
2018-12-03 23:13:11 +00:00
|
|
|
op: op, alias: _joinAlias(), additionalFields: additionalFields));
|
2018-12-01 18:23:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Execute a `RIGHT JOIN` against another table.
|
|
|
|
void rightJoin(String tableName, String localKey, String foreignKey,
|
2018-12-03 23:13:11 +00:00
|
|
|
{String op: '=', List<String> additionalFields: const []}) {
|
|
|
|
_joins.add(new JoinBuilder(
|
2018-12-01 19:12:07 +00:00
|
|
|
JoinType.right, this, tableName, localKey, foreignKey,
|
2018-12-03 23:13:11 +00:00
|
|
|
op: op, alias: _joinAlias(), additionalFields: additionalFields));
|
2018-12-01 18:23:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Execute a `FULL OUTER JOIN` against another table.
|
|
|
|
void fullOuterJoin(String tableName, String localKey, String foreignKey,
|
2018-12-03 23:13:11 +00:00
|
|
|
{String op: '=', List<String> additionalFields: const []}) {
|
|
|
|
_joins.add(new JoinBuilder(
|
2018-12-01 19:12:07 +00:00
|
|
|
JoinType.full, this, tableName, localKey, foreignKey,
|
2018-12-03 23:13:11 +00:00
|
|
|
op: op, alias: _joinAlias(), additionalFields: additionalFields));
|
2018-12-01 18:23:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Execute a `SELF JOIN`.
|
|
|
|
void selfJoin(String tableName, String localKey, String foreignKey,
|
2018-12-03 23:13:11 +00:00
|
|
|
{String op: '=', List<String> additionalFields: const []}) {
|
|
|
|
_joins.add(new JoinBuilder(
|
2018-12-01 19:12:07 +00:00
|
|
|
JoinType.self, this, tableName, localKey, foreignKey,
|
2018-12-03 23:13:11 +00:00
|
|
|
op: op, alias: _joinAlias(), additionalFields: additionalFields));
|
2018-12-01 18:23:50 +00:00
|
|
|
}
|
|
|
|
|
2018-08-24 12:30:38 +00:00
|
|
|
@override
|
2018-12-03 23:13:11 +00:00
|
|
|
String compile(
|
|
|
|
{bool includeTableName: false, String preamble, bool withFields: true}) {
|
|
|
|
includeTableName = includeTableName || _joins.isNotEmpty;
|
2018-12-03 16:50:43 +00:00
|
|
|
var b = new StringBuffer(preamble ?? 'SELECT');
|
|
|
|
b.write(' ');
|
2018-12-03 23:13:11 +00:00
|
|
|
List<String> f;
|
|
|
|
|
|
|
|
if (fields == null) {
|
|
|
|
f = ['*'];
|
|
|
|
} else {
|
|
|
|
f = new List<String>.from(
|
|
|
|
fields.map((s) => includeTableName ? '$tableName.$s' : s));
|
|
|
|
_joins.forEach((j) {
|
|
|
|
f
|
|
|
|
..add(j.fieldName)
|
|
|
|
..addAll(j.additionalFields.map((s) => j.nameFor(s)));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (withFields) b.write(f.join(', '));
|
2018-12-01 17:21:34 +00:00
|
|
|
b.write(' FROM $tableName');
|
2018-12-08 02:14:14 +00:00
|
|
|
|
|
|
|
// No joins if it's not a select.
|
|
|
|
if (preamble == null) {
|
|
|
|
if (_crossJoin != null) b.write(' CROSS JOIN $_crossJoin');
|
|
|
|
for (var join in _joins) b.write(' ${join.compile()}');
|
|
|
|
}
|
|
|
|
|
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()}');
|
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
|
|
|
|
2018-12-03 13:41:14 +00:00
|
|
|
@override
|
|
|
|
Future<T> getOne(QueryExecutor executor) {
|
|
|
|
limit(1);
|
|
|
|
return super.getOne(executor);
|
|
|
|
}
|
|
|
|
|
2018-12-08 02:03:03 +00:00
|
|
|
Future<List<T>> delete(QueryExecutor executor) {
|
2018-12-08 02:14:14 +00:00
|
|
|
var sql = compile(preamble: 'DELETE', withFields: false);
|
|
|
|
|
|
|
|
if (_joins.isEmpty) {
|
|
|
|
return executor
|
|
|
|
.query(sql, fields.map(adornWithTableName).toList())
|
|
|
|
.then((it) => it.map(deserialize).toList());
|
|
|
|
} else {
|
|
|
|
return executor.transaction(() async {
|
|
|
|
// TODO: Can this be done with just *one* query?
|
|
|
|
var existing = await get(executor);
|
|
|
|
//var sql = compile(preamble: 'SELECT $tableName.id', withFields: false);
|
|
|
|
return executor.query(sql).then((_) => existing);
|
|
|
|
});
|
|
|
|
}
|
2018-12-01 22:39:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<T> deleteOne(QueryExecutor executor) {
|
|
|
|
return delete(executor).then((it) => it.isEmpty ? null : it.first);
|
|
|
|
}
|
2018-12-03 13:41:14 +00:00
|
|
|
|
|
|
|
Future<T> insert(QueryExecutor executor) {
|
2018-12-03 16:50:43 +00:00
|
|
|
var sql = values.compileInsert(tableName);
|
2018-12-03 13:41:14 +00:00
|
|
|
|
2018-12-03 16:50:43 +00:00
|
|
|
if (sql == null) {
|
2018-12-03 13:41:14 +00:00
|
|
|
throw new StateError('No values have been specified for update.');
|
|
|
|
} else {
|
|
|
|
return executor
|
2018-12-08 02:03:03 +00:00
|
|
|
.query(sql, fields.map(adornWithTableName).toList())
|
2018-12-03 13:41:14 +00:00
|
|
|
.then((it) => it.isEmpty ? null : deserialize(it.first));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<List<T>> update(QueryExecutor executor) async {
|
2018-12-03 16:50:43 +00:00
|
|
|
var sql = new StringBuffer('UPDATE $tableName ');
|
2018-12-03 13:41:14 +00:00
|
|
|
var valuesClause = values.compileForUpdate();
|
|
|
|
|
|
|
|
if (valuesClause == null) {
|
|
|
|
throw new StateError('No values have been specified for update.');
|
|
|
|
} else {
|
|
|
|
sql.write(' $valuesClause');
|
|
|
|
var whereClause = where.compile();
|
|
|
|
if (whereClause.isNotEmpty) sql.write(' WHERE $whereClause');
|
|
|
|
if (_limit != null) sql.write(' LIMIT $_limit');
|
2018-12-08 02:14:14 +00:00
|
|
|
|
|
|
|
if (_joins.isEmpty) {
|
|
|
|
return executor
|
|
|
|
.query(sql.toString(), fields.map(adornWithTableName).toList())
|
|
|
|
.then((it) => it.map(deserialize).toList());
|
|
|
|
} else {
|
|
|
|
// TODO: Can this be done with just *one* query?
|
|
|
|
return executor
|
|
|
|
.query(sql.toString(), fields.map(adornWithTableName).toList())
|
|
|
|
.then((it) => get(executor));
|
|
|
|
}
|
2018-12-03 13:41:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<T> updateOne(QueryExecutor executor) {
|
|
|
|
return update(executor).then((it) => it.isEmpty ? null : it.first);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract class QueryValues {
|
|
|
|
Map<String, dynamic> toMap();
|
|
|
|
|
2018-12-03 16:50:43 +00:00
|
|
|
String compileInsert(String tableName) {
|
2018-12-03 13:41:14 +00:00
|
|
|
var data = toMap();
|
|
|
|
if (data.isEmpty) return null;
|
2018-12-03 16:50:43 +00:00
|
|
|
|
|
|
|
var fieldSet = data.keys.join(', ');
|
|
|
|
var b = new StringBuffer('INSERT INTO $tableName ($fieldSet) VALUES (');
|
2018-12-03 13:41:14 +00:00
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
for (var entry in data.entries) {
|
|
|
|
if (i++ > 0) b.write(', ');
|
|
|
|
b.write(toSql(entry.value));
|
|
|
|
}
|
|
|
|
|
|
|
|
b.write(')');
|
|
|
|
return b.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
String compileForUpdate() {
|
|
|
|
var data = toMap();
|
|
|
|
if (data.isEmpty) return null;
|
|
|
|
var b = new StringBuffer('SET');
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
for (var entry in data.entries) {
|
|
|
|
if (i++ > 0) b.write(',');
|
|
|
|
b.write(' ');
|
|
|
|
b.write(entry.key);
|
|
|
|
b.write('=');
|
|
|
|
b.write(toSql(entry.value));
|
|
|
|
}
|
|
|
|
return b.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A [QueryValues] implementation that simply writes to a [Map].
|
|
|
|
class MapQueryValues extends QueryValues {
|
|
|
|
final Map<String, dynamic> values = {};
|
|
|
|
|
|
|
|
@override
|
|
|
|
Map<String, dynamic> toMap() => values;
|
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 ');
|
2018-12-03 16:50:43 +00:00
|
|
|
if (builder is DateTimeSqlExpressionBuilder) {
|
|
|
|
if (tableName != null) b.write('$tableName.');
|
|
|
|
b.write(builder.compile());
|
|
|
|
} else {
|
|
|
|
b.write('$key ${builder.compile()}');
|
|
|
|
}
|
2018-12-01 17:21:34 +00:00
|
|
|
}
|
|
|
|
}
|
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-05-04 03:51:17 +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-03 23:13:11 +00:00
|
|
|
String compile(
|
|
|
|
{bool includeTableName: false, String preamble, bool withFields: true}) {
|
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;
|
2018-12-03 23:13:11 +00:00
|
|
|
final String to, key, value, op, alias;
|
|
|
|
final List<String> additionalFields;
|
2018-12-01 18:23:50 +00:00
|
|
|
|
2018-12-01 19:12:07 +00:00
|
|
|
JoinBuilder(this.type, this.from, this.to, this.key, this.value,
|
2018-12-03 23:13:11 +00:00
|
|
|
{this.op: '=', this.alias, this.additionalFields: const []});
|
|
|
|
|
|
|
|
String get fieldName {
|
|
|
|
var right = '$to.$value';
|
|
|
|
if (alias != null) right = '$alias.$value';
|
|
|
|
return right;
|
|
|
|
}
|
|
|
|
|
|
|
|
String nameFor(String name) {
|
|
|
|
var right = '$to.$name';
|
|
|
|
if (alias != null) right = '$alias.$name';
|
|
|
|
return right;
|
|
|
|
}
|
2018-12-01 18:23:50 +00:00
|
|
|
|
|
|
|
String compile() {
|
|
|
|
var b = new StringBuffer();
|
|
|
|
var left = '${from.tableName}.$key';
|
2018-12-03 23:13:11 +00:00
|
|
|
var right = fieldName;
|
2018-12-01 18:23:50 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-12-03 23:13:11 +00:00
|
|
|
b.write(' $to');
|
|
|
|
if (alias != null) b.write(' $alias');
|
|
|
|
b.write(' ON $left$op$right');
|
2018-12-01 18:23:50 +00:00
|
|
|
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-03 16:50:43 +00:00
|
|
|
Future<List<List>> query(String query, [List<String> returningFields]);
|
2018-12-08 02:03:03 +00:00
|
|
|
|
|
|
|
Future<T> transaction<T>(FutureOr<T> f());
|
2018-08-24 12:30:38 +00:00
|
|
|
}
|