commit
2a8a186bca
58 changed files with 1050 additions and 803 deletions
|
@ -1,3 +1,15 @@
|
||||||
|
# 2.1.0-beta
|
||||||
|
* Split the formerly 600+ line `src/query.dart` up into
|
||||||
|
separate files.
|
||||||
|
* **BREAKING**: Add a required `QueryExecutor` argument to `transaction`
|
||||||
|
callbacks.
|
||||||
|
* Make `JoinBuilder` take `to` as a `String Function()`. This will allow
|
||||||
|
ORM queries to reference their joined subqueries.
|
||||||
|
* Removed deprecated `Join`, `toSql`, `sanitizeExpression`, `isAscii`.
|
||||||
|
* Always put `ORDER BY` before `LIMIT`.
|
||||||
|
* `and`, `or`, `not` in `QueryWhere` include parentheses.
|
||||||
|
* Add `joinType` to `Relationship` class.
|
||||||
|
|
||||||
# 2.0.2
|
# 2.0.2
|
||||||
* Place `LIMIT` and `OFFSET` after `ORDER BY`.
|
* Place `LIMIT` and `OFFSET` after `ORDER BY`.
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ class _FakeExecutor extends QueryExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<T> transaction<T>(FutureOr<T> Function() f) {
|
Future<T> transaction<T>(FutureOr<T> Function(QueryExecutor) f) {
|
||||||
throw UnsupportedError('Transactions are not supported.');
|
throw UnsupportedError('Transactions are not supported.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
export 'src/annotations.dart';
|
export 'src/annotations.dart';
|
||||||
export 'src/builder.dart';
|
export 'src/builder.dart';
|
||||||
|
export 'src/join_builder.dart';
|
||||||
|
export 'src/join_on.dart';
|
||||||
|
export 'src/map_query_values.dart';
|
||||||
export 'src/migration.dart';
|
export 'src/migration.dart';
|
||||||
export 'src/relations.dart';
|
export 'src/order_by.dart';
|
||||||
|
export 'src/query_base.dart';
|
||||||
|
export 'src/query_executor.dart';
|
||||||
|
export 'src/query_values.dart';
|
||||||
|
export 'src/query_where.dart';
|
||||||
export 'src/query.dart';
|
export 'src/query.dart';
|
||||||
|
export 'src/relations.dart';
|
||||||
|
export 'src/union.dart';
|
||||||
|
export 'src/util.dart';
|
||||||
|
|
|
@ -27,14 +27,5 @@ class Orm {
|
||||||
const Orm({this.tableName, this.generateMigrations = true});
|
const Orm({this.tableName, this.generateMigrations = true});
|
||||||
}
|
}
|
||||||
|
|
||||||
@deprecated
|
|
||||||
class Join {
|
|
||||||
final Type against;
|
|
||||||
final String foreignKey;
|
|
||||||
final JoinType type;
|
|
||||||
|
|
||||||
const Join(this.against, this.foreignKey, {this.type = JoinType.inner});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The various types of join.
|
/// The various types of join.
|
||||||
enum JoinType { inner, left, right, full, self }
|
enum JoinType { inner, left, right, full, self }
|
||||||
|
|
|
@ -1,41 +1,10 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:charcode/ascii.dart';
|
|
||||||
import 'package:intl/intl.dart' show DateFormat;
|
import 'package:intl/intl.dart' show DateFormat;
|
||||||
import 'package:string_scanner/string_scanner.dart';
|
|
||||||
import 'query.dart';
|
import 'query.dart';
|
||||||
|
|
||||||
final DateFormat dateYmd = DateFormat('yyyy-MM-dd');
|
final DateFormat dateYmd = DateFormat('yyyy-MM-dd');
|
||||||
final DateFormat dateYmdHms = DateFormat('yyyy-MM-dd HH:mm:ss');
|
final DateFormat dateYmdHms = DateFormat('yyyy-MM-dd HH:mm:ss');
|
||||||
|
|
||||||
/// The ORM prefers using substitution values, which allow for prepared queries,
|
|
||||||
/// and prevent SQL injection attacks.
|
|
||||||
@deprecated
|
|
||||||
String sanitizeExpression(String unsafe) {
|
|
||||||
var buf = StringBuffer();
|
|
||||||
var scanner = StringScanner(unsafe);
|
|
||||||
int ch;
|
|
||||||
|
|
||||||
while (!scanner.isDone) {
|
|
||||||
// Ignore comment starts
|
|
||||||
if (scanner.scan('--') || scanner.scan('/*')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore all single quotes and attempted escape sequences
|
|
||||||
else if (scanner.scan("'") || scanner.scan('\\')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, add the next char, unless it's a null byte.
|
|
||||||
else if ((ch = scanner.readChar()) != $nul && ch != null) {
|
|
||||||
buf.writeCharCode(ch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return toSql(buf.toString(), withQuotes: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class SqlExpressionBuilder<T> {
|
abstract class SqlExpressionBuilder<T> {
|
||||||
final Query query;
|
final Query query;
|
||||||
final String columnName;
|
final String columnName;
|
||||||
|
|
67
angel_orm/lib/src/join_builder.dart
Normal file
67
angel_orm/lib/src/join_builder.dart
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import 'annotations.dart';
|
||||||
|
import 'query.dart';
|
||||||
|
|
||||||
|
/// Builds a SQL `JOIN` query.
|
||||||
|
class JoinBuilder {
|
||||||
|
final JoinType type;
|
||||||
|
final Query from;
|
||||||
|
final String key, value, op, alias;
|
||||||
|
|
||||||
|
/// A callback to produces the expression to join against, i.e.
|
||||||
|
/// a table name, or the result of compiling a query.
|
||||||
|
final String Function() to;
|
||||||
|
final List<String> additionalFields;
|
||||||
|
|
||||||
|
JoinBuilder(this.type, this.from, this.to, this.key, this.value,
|
||||||
|
{this.op = '=', this.alias, this.additionalFields = const []}) {
|
||||||
|
assert(to != null,
|
||||||
|
'computation of this join threw an error, and returned null.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
String compile(Set<String> trampoline) {
|
||||||
|
var compiledTo = to();
|
||||||
|
if (compiledTo == null) {
|
||||||
|
print(
|
||||||
|
'NULLLLL $to; from $from; key: $key, value: $value, addl: $additionalFields');
|
||||||
|
}
|
||||||
|
if (compiledTo == null) return null;
|
||||||
|
var b = StringBuffer();
|
||||||
|
var left = '${from.tableName}.$key';
|
||||||
|
var right = fieldName;
|
||||||
|
|
||||||
|
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(' $compiledTo');
|
||||||
|
if (alias != null) b.write(' $alias');
|
||||||
|
b.write(' ON $left$op$right');
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
}
|
8
angel_orm/lib/src/join_on.dart
Normal file
8
angel_orm/lib/src/join_on.dart
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import 'builder.dart';
|
||||||
|
|
||||||
|
class JoinOn {
|
||||||
|
final SqlExpressionBuilder key;
|
||||||
|
final SqlExpressionBuilder value;
|
||||||
|
|
||||||
|
JoinOn(this.key, this.value);
|
||||||
|
}
|
9
angel_orm/lib/src/map_query_values.dart
Normal file
9
angel_orm/lib/src/map_query_values.dart
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import 'query_values.dart';
|
||||||
|
|
||||||
|
/// A [QueryValues] implementation that simply writes to a [Map].
|
||||||
|
class MapQueryValues extends QueryValues {
|
||||||
|
final Map<String, dynamic> values = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toMap() => values;
|
||||||
|
}
|
|
@ -26,11 +26,18 @@ class Column {
|
||||||
/// Specifies what kind of index this column is, if any.
|
/// Specifies what kind of index this column is, if any.
|
||||||
final IndexType indexType;
|
final IndexType indexType;
|
||||||
|
|
||||||
|
/// A custom SQL expression to execute, instead of a named column.
|
||||||
|
final String expression;
|
||||||
|
|
||||||
const Column(
|
const Column(
|
||||||
{this.isNullable = true,
|
{this.isNullable = true,
|
||||||
this.length,
|
this.length,
|
||||||
this.type,
|
this.type,
|
||||||
this.indexType = IndexType.none});
|
this.indexType = IndexType.none,
|
||||||
|
this.expression});
|
||||||
|
|
||||||
|
/// Returns `true` if [expression] is not `null`.
|
||||||
|
bool get hasExpression => expression != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PrimaryKey extends Column {
|
class PrimaryKey extends Column {
|
||||||
|
|
8
angel_orm/lib/src/order_by.dart
Normal file
8
angel_orm/lib/src/order_by.dart
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
class OrderBy {
|
||||||
|
final String key;
|
||||||
|
final bool descending;
|
||||||
|
|
||||||
|
const OrderBy(this.key, {this.descending = false});
|
||||||
|
|
||||||
|
String compile() => descending ? '$key DESC' : '$key ASC';
|
||||||
|
}
|
|
@ -1,115 +1,11 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:charcode/ascii.dart';
|
|
||||||
import 'annotations.dart';
|
import 'annotations.dart';
|
||||||
import 'builder.dart';
|
import 'join_builder.dart';
|
||||||
|
import 'order_by.dart';
|
||||||
bool isAscii(int ch) => ch >= $nul && ch <= $del;
|
import 'query_base.dart';
|
||||||
|
import 'query_executor.dart';
|
||||||
/// A base class for objects that compile to SQL queries, typically within an ORM.
|
import 'query_values.dart';
|
||||||
abstract class QueryBase<T> {
|
import 'query_where.dart';
|
||||||
/// Casts to perform when querying the database.
|
|
||||||
Map<String, String> get casts => {};
|
|
||||||
|
|
||||||
/// Values to insert into a prepared statement.
|
|
||||||
final Map<String, dynamic> substitutionValues = {};
|
|
||||||
|
|
||||||
/// The table against which to execute this query.
|
|
||||||
String get tableName;
|
|
||||||
|
|
||||||
/// The list of fields returned by this query.
|
|
||||||
///
|
|
||||||
/// If it's `null`, then this query will perform a `SELECT *`.
|
|
||||||
List<String> get fields;
|
|
||||||
|
|
||||||
/// A String of all [fields], joined by a comma (`,`).
|
|
||||||
String get fieldSet => fields.map((k) {
|
|
||||||
var cast = casts[k];
|
|
||||||
return cast == null ? k : 'CAST ($k AS $cast)';
|
|
||||||
}).join(', ');
|
|
||||||
|
|
||||||
String compile(Set<String> trampoline,
|
|
||||||
{bool includeTableName = false, String preamble, bool withFields = true});
|
|
||||||
|
|
||||||
T deserialize(List row);
|
|
||||||
|
|
||||||
Future<List<T>> get(QueryExecutor executor) async {
|
|
||||||
var sql = compile(Set());
|
|
||||||
return executor
|
|
||||||
.query(tableName, sql, substitutionValues)
|
|
||||||
.then((it) => it.map(deserialize).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<T> getOne(QueryExecutor executor) {
|
|
||||||
return get(executor).then((it) => it.isEmpty ? null : it.first);
|
|
||||||
}
|
|
||||||
|
|
||||||
Union<T> union(QueryBase<T> other) {
|
|
||||||
return Union(this, other);
|
|
||||||
}
|
|
||||||
|
|
||||||
Union<T> unionAll(QueryBase<T> other) {
|
|
||||||
return Union(this, other, all: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class OrderBy {
|
|
||||||
final String key;
|
|
||||||
final bool descending;
|
|
||||||
|
|
||||||
const OrderBy(this.key, {this.descending = false});
|
|
||||||
|
|
||||||
String compile() => descending ? '$key DESC' : '$key ASC';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The ORM prefers using substitution values, which allow for prepared queries,
|
|
||||||
/// and prevent SQL injection attacks.
|
|
||||||
@deprecated
|
|
||||||
String toSql(Object obj, {bool withQuotes = true}) {
|
|
||||||
if (obj is DateTime) {
|
|
||||||
return withQuotes ? "'${dateYmdHms.format(obj)}'" : dateYmdHms.format(obj);
|
|
||||||
} else if (obj is bool) {
|
|
||||||
return obj ? 'TRUE' : 'FALSE';
|
|
||||||
} else if (obj == null) {
|
|
||||||
return 'NULL';
|
|
||||||
} else if (obj is String) {
|
|
||||||
var b = StringBuffer();
|
|
||||||
var escaped = false;
|
|
||||||
var it = obj.runes.iterator;
|
|
||||||
|
|
||||||
while (it.moveNext()) {
|
|
||||||
if (it.current == $nul) {
|
|
||||||
continue; // Skip null byte
|
|
||||||
} 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)) {
|
|
||||||
b.writeCharCode(it.current);
|
|
||||||
} else if (it.currentSize == 1) {
|
|
||||||
escaped = true;
|
|
||||||
b.write('\\u');
|
|
||||||
b.write(it.current.toRadixString(16).padLeft(4, '0'));
|
|
||||||
} else if (it.currentSize == 2) {
|
|
||||||
escaped = true;
|
|
||||||
b.write('\\U');
|
|
||||||
b.write(it.current.toRadixString(16).padLeft(8, '0'));
|
|
||||||
} else {
|
|
||||||
throw UnsupportedError(
|
|
||||||
'toSql() cannot encode a rune of size (${it.currentSize})');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!withQuotes) {
|
|
||||||
return b.toString();
|
|
||||||
} else if (escaped) {
|
|
||||||
return "E'$b'";
|
|
||||||
} else {
|
|
||||||
return "'$b'";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return obj.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A SQL `SELECT` query builder.
|
/// A SQL `SELECT` query builder.
|
||||||
abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
||||||
|
@ -117,9 +13,18 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
||||||
final Map<String, int> _names = {};
|
final Map<String, int> _names = {};
|
||||||
final List<OrderBy> _orderBy = [];
|
final List<OrderBy> _orderBy = [];
|
||||||
|
|
||||||
|
// An optional "parent query". If provided, [reserveName] will operate in
|
||||||
|
// the parent's context.
|
||||||
|
final Query parent;
|
||||||
|
|
||||||
String _crossJoin, _groupBy;
|
String _crossJoin, _groupBy;
|
||||||
int _limit, _offset;
|
int _limit, _offset;
|
||||||
|
|
||||||
|
Query({this.parent});
|
||||||
|
|
||||||
|
Map<String, dynamic> get substitutionValues =>
|
||||||
|
parent?.substitutionValues ?? super.substitutionValues;
|
||||||
|
|
||||||
/// A reference to an abstract query builder.
|
/// A reference to an abstract query builder.
|
||||||
///
|
///
|
||||||
/// This is usually a generated class.
|
/// This is usually a generated class.
|
||||||
|
@ -136,6 +41,7 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
||||||
/// Returns a unique version of [name], which will not produce a collision within
|
/// Returns a unique version of [name], which will not produce a collision within
|
||||||
/// the context of this [query].
|
/// the context of this [query].
|
||||||
String reserveName(String name) {
|
String reserveName(String name) {
|
||||||
|
if (parent != null) return parent.reserveName(name);
|
||||||
var n = _names[name] ??= 0;
|
var n = _names[name] ??= 0;
|
||||||
_names[name]++;
|
_names[name]++;
|
||||||
return n == 0 ? name : '${name}$n';
|
return n == 0 ? name : '${name}$n';
|
||||||
|
@ -211,13 +117,15 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _compileJoin(tableName, Set<String> trampoline) {
|
String Function() _compileJoin(tableName, Set<String> trampoline) {
|
||||||
if (tableName is String) {
|
if (tableName is String) {
|
||||||
return tableName;
|
return () => tableName;
|
||||||
} else if (tableName is Query) {
|
} else if (tableName is Query) {
|
||||||
var c = tableName.compile(trampoline);
|
return () {
|
||||||
if (c == null) return c;
|
var c = tableName.compile(trampoline);
|
||||||
return '($c)';
|
if (c == null) return c;
|
||||||
|
return '($c)';
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
throw ArgumentError.value(
|
throw ArgumentError.value(
|
||||||
tableName, 'tableName', 'must be a String or Query');
|
tableName, 'tableName', 'must be a String or Query');
|
||||||
|
@ -311,6 +219,8 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
||||||
b.write(' ');
|
b.write(' ');
|
||||||
List<String> f;
|
List<String> f;
|
||||||
|
|
||||||
|
var compiledJoins = <JoinBuilder, String>{};
|
||||||
|
|
||||||
if (fields == null) {
|
if (fields == null) {
|
||||||
f = ['*'];
|
f = ['*'];
|
||||||
} else {
|
} else {
|
||||||
|
@ -321,10 +231,16 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
||||||
return ss;
|
return ss;
|
||||||
}));
|
}));
|
||||||
_joins.forEach((j) {
|
_joins.forEach((j) {
|
||||||
var additional = j.additionalFields.map(j.nameFor).toList();
|
var c = compiledJoins[j] = j.compile(trampoline);
|
||||||
// if (!additional.contains(j.fieldName))
|
if (c != null) {
|
||||||
// additional.insert(0, j.fieldName);
|
var additional = j.additionalFields.map(j.nameFor).toList();
|
||||||
f.addAll(additional);
|
f.addAll(additional);
|
||||||
|
} else {
|
||||||
|
// If compilation failed, fill in NULL placeholders.
|
||||||
|
for (var i = 0; i < j.additionalFields.length; i++) {
|
||||||
|
f.add('NULL');
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (withFields) b.write(f.join(', '));
|
if (withFields) b.write(f.join(', '));
|
||||||
|
@ -335,7 +251,7 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
||||||
if (preamble == null) {
|
if (preamble == null) {
|
||||||
if (_crossJoin != null) b.write(' CROSS JOIN $_crossJoin');
|
if (_crossJoin != null) b.write(' CROSS JOIN $_crossJoin');
|
||||||
for (var join in _joins) {
|
for (var join in _joins) {
|
||||||
var c = join.compile(trampoline);
|
var c = compiledJoins[join];
|
||||||
if (c != null) b.write(' $c');
|
if (c != null) b.write(' $c');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -367,11 +283,11 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
||||||
fields.map(adornWithTableName).toList())
|
fields.map(adornWithTableName).toList())
|
||||||
.then((it) => it.map(deserialize).toList());
|
.then((it) => it.map(deserialize).toList());
|
||||||
} else {
|
} else {
|
||||||
return executor.transaction(() async {
|
return executor.transaction((tx) async {
|
||||||
// TODO: Can this be done with just *one* query?
|
// TODO: Can this be done with just *one* query?
|
||||||
var existing = await get(executor);
|
var existing = await get(tx);
|
||||||
//var sql = compile(preamble: 'SELECT $tableName.id', withFields: false);
|
//var sql = compile(preamble: 'SELECT $tableName.id', withFields: false);
|
||||||
return executor
|
return tx
|
||||||
.query(tableName, sql, substitutionValues)
|
.query(tableName, sql, substitutionValues)
|
||||||
.then((_) => existing);
|
.then((_) => existing);
|
||||||
});
|
});
|
||||||
|
@ -424,241 +340,3 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
||||||
return update(executor).then((it) => it.isEmpty ? null : it.first);
|
return update(executor).then((it) => it.isEmpty ? null : it.first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class QueryValues {
|
|
||||||
Map<String, String> get casts => {};
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap();
|
|
||||||
|
|
||||||
String applyCast(String name, String sub) {
|
|
||||||
if (casts.containsKey(name)) {
|
|
||||||
var type = casts[name];
|
|
||||||
return 'CAST ($sub as $type)';
|
|
||||||
} else {
|
|
||||||
return sub;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String compileInsert(Query query, String tableName) {
|
|
||||||
var data = Map<String, dynamic>.from(toMap());
|
|
||||||
var keys = data.keys.toList();
|
|
||||||
keys.where((k) => !query.fields.contains(k)).forEach(data.remove);
|
|
||||||
if (data.isEmpty) return null;
|
|
||||||
|
|
||||||
var fieldSet = data.keys.join(', ');
|
|
||||||
var b = StringBuffer('INSERT INTO $tableName ($fieldSet) VALUES (');
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
for (var entry in data.entries) {
|
|
||||||
if (i++ > 0) b.write(', ');
|
|
||||||
|
|
||||||
var name = query.reserveName(entry.key);
|
|
||||||
var s = applyCast(entry.key, '@$name');
|
|
||||||
query.substitutionValues[name] = entry.value;
|
|
||||||
b.write(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
b.write(')');
|
|
||||||
return b.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
String compileForUpdate(Query query) {
|
|
||||||
var data = toMap();
|
|
||||||
if (data.isEmpty) return null;
|
|
||||||
var b = StringBuffer('SET');
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
for (var entry in data.entries) {
|
|
||||||
if (i++ > 0) b.write(',');
|
|
||||||
b.write(' ');
|
|
||||||
b.write(entry.key);
|
|
||||||
b.write('=');
|
|
||||||
|
|
||||||
var name = query.reserveName(entry.key);
|
|
||||||
var s = applyCast(entry.key, '@$name');
|
|
||||||
query.substitutionValues[name] = entry.value;
|
|
||||||
b.write(s);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds a SQL `WHERE` clause.
|
|
||||||
abstract class QueryWhere {
|
|
||||||
final Set<QueryWhere> _and = Set();
|
|
||||||
final Set<QueryWhere> _not = Set();
|
|
||||||
final Set<QueryWhere> _or = Set();
|
|
||||||
|
|
||||||
Iterable<SqlExpressionBuilder> get expressionBuilders;
|
|
||||||
|
|
||||||
void and(QueryWhere other) {
|
|
||||||
_and.add(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
void not(QueryWhere other) {
|
|
||||||
_not.add(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
void or(QueryWhere other) {
|
|
||||||
_or.add(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
String compile({String tableName}) {
|
|
||||||
var b = StringBuffer();
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
for (var builder in expressionBuilders) {
|
|
||||||
var key = builder.columnName;
|
|
||||||
if (tableName != null) key = '$tableName.$key';
|
|
||||||
if (builder.hasValue) {
|
|
||||||
if (i++ > 0) b.write(' AND ');
|
|
||||||
if (builder is DateTimeSqlExpressionBuilder ||
|
|
||||||
(builder is JsonSqlExpressionBuilder && builder.hasRaw)) {
|
|
||||||
if (tableName != null) b.write('$tableName.');
|
|
||||||
b.write(builder.compile());
|
|
||||||
} else {
|
|
||||||
b.write('$key ${builder.compile()}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var other in _and) {
|
|
||||||
var sql = other.compile();
|
|
||||||
if (sql.isNotEmpty) b.write(' AND $sql');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var other in _not) {
|
|
||||||
var sql = other.compile();
|
|
||||||
if (sql.isNotEmpty) b.write(' NOT $sql');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var other in _or) {
|
|
||||||
var sql = other.compile();
|
|
||||||
if (sql.isNotEmpty) b.write(' OR $sql');
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the `UNION` of two subqueries.
|
|
||||||
class Union<T> extends QueryBase<T> {
|
|
||||||
/// The subject(s) of this binary operation.
|
|
||||||
final QueryBase<T> left, right;
|
|
||||||
|
|
||||||
/// Whether this is a `UNION ALL` operation.
|
|
||||||
final bool all;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final String tableName;
|
|
||||||
|
|
||||||
Union(this.left, this.right, {this.all = false, String tableName})
|
|
||||||
: this.tableName = tableName ?? left.tableName {
|
|
||||||
substitutionValues
|
|
||||||
..addAll(left.substitutionValues)
|
|
||||||
..addAll(right.substitutionValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<String> get fields => left.fields;
|
|
||||||
|
|
||||||
@override
|
|
||||||
T deserialize(List row) => left.deserialize(row);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String compile(Set<String> trampoline,
|
|
||||||
{bool includeTableName = false,
|
|
||||||
String preamble,
|
|
||||||
bool withFields = true}) {
|
|
||||||
var selector = all == true ? 'UNION ALL' : 'UNION';
|
|
||||||
var t1 = Set<String>.from(trampoline);
|
|
||||||
var t2 = Set<String>.from(trampoline);
|
|
||||||
return '(${left.compile(t1, includeTableName: includeTableName)}) $selector (${right.compile(t2, includeTableName: includeTableName)})';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds a SQL `JOIN` query.
|
|
||||||
class JoinBuilder {
|
|
||||||
final JoinType type;
|
|
||||||
final Query from;
|
|
||||||
final String to, key, value, op, alias;
|
|
||||||
final List<String> additionalFields;
|
|
||||||
|
|
||||||
JoinBuilder(this.type, this.from, this.to, this.key, this.value,
|
|
||||||
{this.op = '=', this.alias, this.additionalFields = const []}) {
|
|
||||||
assert(to != null,
|
|
||||||
'computation of this join threw an error, and returned null.');
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
String compile(Set<String> trampoline) {
|
|
||||||
if (to == null) return null;
|
|
||||||
var b = StringBuffer();
|
|
||||||
var left = '${from.tableName}.$key';
|
|
||||||
var right = fieldName;
|
|
||||||
|
|
||||||
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');
|
|
||||||
if (alias != null) b.write(' $alias');
|
|
||||||
b.write(' ON $left$op$right');
|
|
||||||
return b.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class JoinOn {
|
|
||||||
final SqlExpressionBuilder key;
|
|
||||||
final SqlExpressionBuilder value;
|
|
||||||
|
|
||||||
JoinOn(this.key, this.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An abstract interface that performs queries.
|
|
||||||
///
|
|
||||||
/// This class should be implemented.
|
|
||||||
abstract class QueryExecutor {
|
|
||||||
const QueryExecutor();
|
|
||||||
|
|
||||||
/// Executes a single query.
|
|
||||||
Future<List<List>> query(
|
|
||||||
String tableName, String query, Map<String, dynamic> substitutionValues,
|
|
||||||
[List<String> returningFields]);
|
|
||||||
|
|
||||||
/// Begins a database transaction.
|
|
||||||
Future<T> transaction<T>(FutureOr<T> f());
|
|
||||||
}
|
|
||||||
|
|
50
angel_orm/lib/src/query_base.dart
Normal file
50
angel_orm/lib/src/query_base.dart
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'query_executor.dart';
|
||||||
|
import 'union.dart';
|
||||||
|
|
||||||
|
/// A base class for objects that compile to SQL queries, typically within an ORM.
|
||||||
|
abstract class QueryBase<T> {
|
||||||
|
/// Casts to perform when querying the database.
|
||||||
|
Map<String, String> get casts => {};
|
||||||
|
|
||||||
|
/// Values to insert into a prepared statement.
|
||||||
|
final Map<String, dynamic> substitutionValues = {};
|
||||||
|
|
||||||
|
/// The table against which to execute this query.
|
||||||
|
String get tableName;
|
||||||
|
|
||||||
|
/// The list of fields returned by this query.
|
||||||
|
///
|
||||||
|
/// If it's `null`, then this query will perform a `SELECT *`.
|
||||||
|
List<String> get fields;
|
||||||
|
|
||||||
|
/// A String of all [fields], joined by a comma (`,`).
|
||||||
|
String get fieldSet => fields.map((k) {
|
||||||
|
var cast = casts[k];
|
||||||
|
return cast == null ? k : 'CAST ($k AS $cast)';
|
||||||
|
}).join(', ');
|
||||||
|
|
||||||
|
String compile(Set<String> trampoline,
|
||||||
|
{bool includeTableName = false, String preamble, bool withFields = true});
|
||||||
|
|
||||||
|
T deserialize(List row);
|
||||||
|
|
||||||
|
Future<List<T>> get(QueryExecutor executor) async {
|
||||||
|
var sql = compile(Set());
|
||||||
|
return executor
|
||||||
|
.query(tableName, sql, substitutionValues)
|
||||||
|
.then((it) => it.map(deserialize).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T> getOne(QueryExecutor executor) {
|
||||||
|
return get(executor).then((it) => it.isEmpty ? null : it.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
Union<T> union(QueryBase<T> other) {
|
||||||
|
return Union(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
Union<T> unionAll(QueryBase<T> other) {
|
||||||
|
return Union(this, other, all: true);
|
||||||
|
}
|
||||||
|
}
|
23
angel_orm/lib/src/query_executor.dart
Normal file
23
angel_orm/lib/src/query_executor.dart
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
/// An abstract interface that performs queries.
|
||||||
|
///
|
||||||
|
/// This class should be implemented.
|
||||||
|
abstract class QueryExecutor {
|
||||||
|
const QueryExecutor();
|
||||||
|
|
||||||
|
/// Executes a single query.
|
||||||
|
Future<List<List>> query(
|
||||||
|
String tableName, String query, Map<String, dynamic> substitutionValues,
|
||||||
|
[List<String> returningFields]);
|
||||||
|
|
||||||
|
/// Enters a database transaction, performing the actions within,
|
||||||
|
/// and returning the results of [f].
|
||||||
|
///
|
||||||
|
/// If [f] fails, the transaction will be rolled back, and the
|
||||||
|
/// responsible exception will be re-thrown.
|
||||||
|
///
|
||||||
|
/// Whether nested transactions are supported depends on the
|
||||||
|
/// underlying driver.
|
||||||
|
Future<T> transaction<T>(FutureOr<T> Function(QueryExecutor) f);
|
||||||
|
}
|
59
angel_orm/lib/src/query_values.dart
Normal file
59
angel_orm/lib/src/query_values.dart
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import 'query.dart';
|
||||||
|
|
||||||
|
abstract class QueryValues {
|
||||||
|
Map<String, String> get casts => {};
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap();
|
||||||
|
|
||||||
|
String applyCast(String name, String sub) {
|
||||||
|
if (casts.containsKey(name)) {
|
||||||
|
var type = casts[name];
|
||||||
|
return 'CAST ($sub as $type)';
|
||||||
|
} else {
|
||||||
|
return sub;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String compileInsert(Query query, String tableName) {
|
||||||
|
var data = Map<String, dynamic>.from(toMap());
|
||||||
|
var keys = data.keys.toList();
|
||||||
|
keys.where((k) => !query.fields.contains(k)).forEach(data.remove);
|
||||||
|
if (data.isEmpty) return null;
|
||||||
|
|
||||||
|
var fieldSet = data.keys.join(', ');
|
||||||
|
var b = StringBuffer('INSERT INTO $tableName ($fieldSet) VALUES (');
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
for (var entry in data.entries) {
|
||||||
|
if (i++ > 0) b.write(', ');
|
||||||
|
|
||||||
|
var name = query.reserveName(entry.key);
|
||||||
|
var s = applyCast(entry.key, '@$name');
|
||||||
|
query.substitutionValues[name] = entry.value;
|
||||||
|
b.write(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
b.write(')');
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String compileForUpdate(Query query) {
|
||||||
|
var data = toMap();
|
||||||
|
if (data.isEmpty) return null;
|
||||||
|
var b = StringBuffer('SET');
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
for (var entry in data.entries) {
|
||||||
|
if (i++ > 0) b.write(',');
|
||||||
|
b.write(' ');
|
||||||
|
b.write(entry.key);
|
||||||
|
b.write('=');
|
||||||
|
|
||||||
|
var name = query.reserveName(entry.key);
|
||||||
|
var s = applyCast(entry.key, '@$name');
|
||||||
|
query.substitutionValues[name] = entry.value;
|
||||||
|
b.write(s);
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
}
|
59
angel_orm/lib/src/query_where.dart
Normal file
59
angel_orm/lib/src/query_where.dart
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import 'builder.dart';
|
||||||
|
|
||||||
|
/// Builds a SQL `WHERE` clause.
|
||||||
|
abstract class QueryWhere {
|
||||||
|
final Set<QueryWhere> _and = Set();
|
||||||
|
final Set<QueryWhere> _not = Set();
|
||||||
|
final Set<QueryWhere> _or = Set();
|
||||||
|
|
||||||
|
Iterable<SqlExpressionBuilder> get expressionBuilders;
|
||||||
|
|
||||||
|
void and(QueryWhere other) {
|
||||||
|
_and.add(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
void not(QueryWhere other) {
|
||||||
|
_not.add(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
void or(QueryWhere other) {
|
||||||
|
_or.add(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
String compile({String tableName}) {
|
||||||
|
var b = StringBuffer();
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
for (var builder in expressionBuilders) {
|
||||||
|
var key = builder.columnName;
|
||||||
|
if (tableName != null) key = '$tableName.$key';
|
||||||
|
if (builder.hasValue) {
|
||||||
|
if (i++ > 0) b.write(' AND ');
|
||||||
|
if (builder is DateTimeSqlExpressionBuilder ||
|
||||||
|
(builder is JsonSqlExpressionBuilder && builder.hasRaw)) {
|
||||||
|
if (tableName != null) b.write('$tableName.');
|
||||||
|
b.write(builder.compile());
|
||||||
|
} else {
|
||||||
|
b.write('$key ${builder.compile()}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var other in _and) {
|
||||||
|
var sql = other.compile();
|
||||||
|
if (sql.isNotEmpty) b.write(' AND ($sql)');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var other in _not) {
|
||||||
|
var sql = other.compile();
|
||||||
|
if (sql.isNotEmpty) b.write(' NOT ($sql)');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var other in _or) {
|
||||||
|
var sql = other.compile();
|
||||||
|
if (sql.isNotEmpty) b.write(' OR ($sql)');
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'annotations.dart';
|
||||||
|
|
||||||
abstract class RelationshipType {
|
abstract class RelationshipType {
|
||||||
static const int hasMany = 0;
|
static const int hasMany = 0;
|
||||||
static const int hasOne = 1;
|
static const int hasOne = 1;
|
||||||
|
@ -11,12 +13,14 @@ class Relationship {
|
||||||
final String foreignKey;
|
final String foreignKey;
|
||||||
final String foreignTable;
|
final String foreignTable;
|
||||||
final bool cascadeOnDelete;
|
final bool cascadeOnDelete;
|
||||||
|
final JoinType joinType;
|
||||||
|
|
||||||
const Relationship(this.type,
|
const Relationship(this.type,
|
||||||
{this.localKey,
|
{this.localKey,
|
||||||
this.foreignKey,
|
this.foreignKey,
|
||||||
this.foreignTable,
|
this.foreignTable,
|
||||||
this.cascadeOnDelete});
|
this.cascadeOnDelete,
|
||||||
|
this.joinType});
|
||||||
}
|
}
|
||||||
|
|
||||||
class HasMany extends Relationship {
|
class HasMany extends Relationship {
|
||||||
|
@ -24,12 +28,14 @@ class HasMany extends Relationship {
|
||||||
{String localKey,
|
{String localKey,
|
||||||
String foreignKey,
|
String foreignKey,
|
||||||
String foreignTable,
|
String foreignTable,
|
||||||
bool cascadeOnDelete = false})
|
bool cascadeOnDelete = false,
|
||||||
|
JoinType joinType})
|
||||||
: super(RelationshipType.hasMany,
|
: super(RelationshipType.hasMany,
|
||||||
localKey: localKey,
|
localKey: localKey,
|
||||||
foreignKey: foreignKey,
|
foreignKey: foreignKey,
|
||||||
foreignTable: foreignTable,
|
foreignTable: foreignTable,
|
||||||
cascadeOnDelete: cascadeOnDelete == true);
|
cascadeOnDelete: cascadeOnDelete == true,
|
||||||
|
joinType: joinType);
|
||||||
}
|
}
|
||||||
|
|
||||||
const HasMany hasMany = HasMany();
|
const HasMany hasMany = HasMany();
|
||||||
|
@ -39,22 +45,29 @@ class HasOne extends Relationship {
|
||||||
{String localKey,
|
{String localKey,
|
||||||
String foreignKey,
|
String foreignKey,
|
||||||
String foreignTable,
|
String foreignTable,
|
||||||
bool cascadeOnDelete = false})
|
bool cascadeOnDelete = false,
|
||||||
|
JoinType joinType})
|
||||||
: super(RelationshipType.hasOne,
|
: super(RelationshipType.hasOne,
|
||||||
localKey: localKey,
|
localKey: localKey,
|
||||||
foreignKey: foreignKey,
|
foreignKey: foreignKey,
|
||||||
foreignTable: foreignTable,
|
foreignTable: foreignTable,
|
||||||
cascadeOnDelete: cascadeOnDelete == true);
|
cascadeOnDelete: cascadeOnDelete == true,
|
||||||
|
joinType: joinType);
|
||||||
}
|
}
|
||||||
|
|
||||||
const HasOne hasOne = HasOne();
|
const HasOne hasOne = HasOne();
|
||||||
|
|
||||||
class BelongsTo extends Relationship {
|
class BelongsTo extends Relationship {
|
||||||
const BelongsTo({String localKey, String foreignKey, String foreignTable})
|
const BelongsTo(
|
||||||
|
{String localKey,
|
||||||
|
String foreignKey,
|
||||||
|
String foreignTable,
|
||||||
|
JoinType joinType})
|
||||||
: super(RelationshipType.belongsTo,
|
: super(RelationshipType.belongsTo,
|
||||||
localKey: localKey,
|
localKey: localKey,
|
||||||
foreignKey: foreignKey,
|
foreignKey: foreignKey,
|
||||||
foreignTable: foreignTable);
|
foreignTable: foreignTable,
|
||||||
|
joinType: joinType);
|
||||||
}
|
}
|
||||||
|
|
||||||
const BelongsTo belongsTo = BelongsTo();
|
const BelongsTo belongsTo = BelongsTo();
|
||||||
|
@ -66,11 +79,13 @@ class ManyToMany extends Relationship {
|
||||||
{String localKey,
|
{String localKey,
|
||||||
String foreignKey,
|
String foreignKey,
|
||||||
String foreignTable,
|
String foreignTable,
|
||||||
bool cascadeOnDelete = false})
|
bool cascadeOnDelete = false,
|
||||||
|
JoinType joinType})
|
||||||
: super(
|
: super(
|
||||||
RelationshipType.hasMany, // Many-to-Many is actually just a hasMany
|
RelationshipType.hasMany, // Many-to-Many is actually just a hasMany
|
||||||
localKey: localKey,
|
localKey: localKey,
|
||||||
foreignKey: foreignKey,
|
foreignKey: foreignKey,
|
||||||
foreignTable: foreignTable,
|
foreignTable: foreignTable,
|
||||||
cascadeOnDelete: cascadeOnDelete == true);
|
cascadeOnDelete: cascadeOnDelete == true,
|
||||||
|
joinType: joinType);
|
||||||
}
|
}
|
||||||
|
|
37
angel_orm/lib/src/union.dart
Normal file
37
angel_orm/lib/src/union.dart
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import 'query_base.dart';
|
||||||
|
|
||||||
|
/// Represents the `UNION` of two subqueries.
|
||||||
|
class Union<T> extends QueryBase<T> {
|
||||||
|
/// The subject(s) of this binary operation.
|
||||||
|
final QueryBase<T> left, right;
|
||||||
|
|
||||||
|
/// Whether this is a `UNION ALL` operation.
|
||||||
|
final bool all;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String tableName;
|
||||||
|
|
||||||
|
Union(this.left, this.right, {this.all = false, String tableName})
|
||||||
|
: this.tableName = tableName ?? left.tableName {
|
||||||
|
substitutionValues
|
||||||
|
..addAll(left.substitutionValues)
|
||||||
|
..addAll(right.substitutionValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> get fields => left.fields;
|
||||||
|
|
||||||
|
@override
|
||||||
|
T deserialize(List row) => left.deserialize(row);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String compile(Set<String> trampoline,
|
||||||
|
{bool includeTableName = false,
|
||||||
|
String preamble,
|
||||||
|
bool withFields = true}) {
|
||||||
|
var selector = all == true ? 'UNION ALL' : 'UNION';
|
||||||
|
var t1 = Set<String>.from(trampoline);
|
||||||
|
var t2 = Set<String>.from(trampoline);
|
||||||
|
return '(${left.compile(t1, includeTableName: includeTableName)}) $selector (${right.compile(t2, includeTableName: includeTableName)})';
|
||||||
|
}
|
||||||
|
}
|
3
angel_orm/lib/src/util.dart
Normal file
3
angel_orm/lib/src/util.dart
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import 'package:charcode/ascii.dart';
|
||||||
|
|
||||||
|
bool isAscii(int ch) => ch >= $nul && ch <= $del;
|
|
@ -1,10 +1,10 @@
|
||||||
name: angel_orm
|
name: angel_orm
|
||||||
version: 2.0.2
|
version: 2.1.0-beta
|
||||||
description: Runtime support for Angel's ORM. Includes base classes for queries.
|
description: Runtime support for Angel's ORM. Includes base classes for queries.
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
homepage: https://github.com/angel-dart/orm
|
homepage: https://github.com/angel-dart/orm
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.0.0-dev.1.2 <3.0.0'
|
sdk: '>=2.0.0 <3.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
charcode: ^1.0.0
|
charcode: ^1.0.0
|
||||||
intl: ^0.15.7
|
intl: ^0.15.7
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
|
# 2.1.0-beta.1
|
||||||
|
* `OrmBuildContext` caching is now local to a `Builder`, so `watch`
|
||||||
|
*should* finally always run when required. Should resolve
|
||||||
|
[#85](https://github.com/angel-dart/orm/issues/85).
|
||||||
|
|
||||||
|
# 2.1.0-beta
|
||||||
|
* Relationships have always generated subqueries; now these subqueries are
|
||||||
|
available as `Query` objects on generated classes.
|
||||||
|
* Support explicitly-defined join types for relations.
|
||||||
|
|
||||||
# 2.0.5
|
# 2.0.5
|
||||||
* Remove `ShimFieldImpl` check, which broke relations.
|
* Remove `ShimFieldImpl` check, which broke relations.
|
||||||
* Fix bug where primary key type would not be emitted in migrations.
|
* Fix bug where primary key type would not be emitted in migrations.
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
include: package:pedantic/analysis_options.yaml
|
||||||
analyzer:
|
analyzer:
|
||||||
strong-mode:
|
strong-mode:
|
||||||
implicit-casts: false
|
implicit-casts: false
|
|
@ -7,13 +7,13 @@ import 'package:angel_serialize/angel_serialize.dart';
|
||||||
part 'main.g.dart';
|
part 'main.g.dart';
|
||||||
|
|
||||||
main() async {
|
main() async {
|
||||||
var query = new EmployeeQuery()
|
var query = EmployeeQuery()
|
||||||
..where.firstName.equals('Rich')
|
..where.firstName.equals('Rich')
|
||||||
..where.lastName.equals('Person')
|
..where.lastName.equals('Person')
|
||||||
..orWhere((w) => w.salary.greaterThanOrEqualTo(75000))
|
..orWhere((w) => w.salary.greaterThanOrEqualTo(75000))
|
||||||
..join('companies', 'company_id', 'id');
|
..join('companies', 'company_id', 'id');
|
||||||
|
|
||||||
var richPerson = await query.getOne(new _FakeExecutor());
|
var richPerson = await query.getOne(_FakeExecutor());
|
||||||
print(richPerson.toJson());
|
print(richPerson.toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ class _FakeExecutor extends QueryExecutor {
|
||||||
Future<List<List>> query(
|
Future<List<List>> query(
|
||||||
String tableName, String query, Map<String, dynamic> substitutionValues,
|
String tableName, String query, Map<String, dynamic> substitutionValues,
|
||||||
[returningFields]) async {
|
[returningFields]) async {
|
||||||
var now = new DateTime.now();
|
var now = DateTime.now();
|
||||||
print(
|
print(
|
||||||
'_FakeExecutor received query: $query and values: $substitutionValues');
|
'_FakeExecutor received query: $query and values: $substitutionValues');
|
||||||
return [
|
return [
|
||||||
|
@ -33,8 +33,8 @@ class _FakeExecutor extends QueryExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<T> transaction<T>(FutureOr<T> Function() f) {
|
Future<T> transaction<T>(FutureOr<T> Function(QueryExecutor) f) {
|
||||||
throw new UnsupportedError('Transactions are not supported.');
|
throw UnsupportedError('Transactions are not supported.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,14 +11,14 @@ import 'package:source_gen/source_gen.dart' hide LibraryBuilder;
|
||||||
import 'orm_build_context.dart';
|
import 'orm_build_context.dart';
|
||||||
|
|
||||||
Builder migrationBuilder(BuilderOptions options) {
|
Builder migrationBuilder(BuilderOptions options) {
|
||||||
return new SharedPartBuilder([
|
return SharedPartBuilder([
|
||||||
new MigrationGenerator(
|
MigrationGenerator(
|
||||||
autoSnakeCaseNames: options.config['auto_snake_case_names'] != false)
|
autoSnakeCaseNames: options.config['auto_snake_case_names'] != false)
|
||||||
], 'angel_migration');
|
], 'angel_migration');
|
||||||
}
|
}
|
||||||
|
|
||||||
class MigrationGenerator extends GeneratorForAnnotation<Orm> {
|
class MigrationGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
static final Parameter _schemaParam = new Parameter((b) => b
|
static final Parameter _schemaParam = Parameter((b) => b
|
||||||
..name = 'schema'
|
..name = 'schema'
|
||||||
..type = refer('Schema'));
|
..type = refer('Schema'));
|
||||||
static final Reference _schema = refer('schema');
|
static final Reference _schema = refer('schema');
|
||||||
|
@ -26,13 +26,14 @@ class MigrationGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
/// If `true` (default), then field names will automatically be (de)serialized as snake_case.
|
/// If `true` (default), then field names will automatically be (de)serialized as snake_case.
|
||||||
final bool autoSnakeCaseNames;
|
final bool autoSnakeCaseNames;
|
||||||
|
|
||||||
const MigrationGenerator({this.autoSnakeCaseNames: true});
|
const MigrationGenerator({this.autoSnakeCaseNames = true});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> generateForAnnotatedElement(
|
Future<String> generateForAnnotatedElement(
|
||||||
Element element, ConstantReader annotation, BuildStep buildStep) async {
|
Element element, ConstantReader annotation, BuildStep buildStep) async {
|
||||||
if (element is! ClassElement)
|
if (element is! ClassElement) {
|
||||||
throw 'Only classes can be annotated with @ORM().';
|
throw 'Only classes can be annotated with @ORM().';
|
||||||
|
}
|
||||||
|
|
||||||
var generateMigrations =
|
var generateMigrations =
|
||||||
annotation.peek('generateMigrations')?.boolValue ?? true;
|
annotation.peek('generateMigrations')?.boolValue ?? true;
|
||||||
|
@ -42,18 +43,18 @@ class MigrationGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}
|
}
|
||||||
|
|
||||||
var resolver = await buildStep.resolver;
|
var resolver = await buildStep.resolver;
|
||||||
var ctx = await buildOrmContext(element as ClassElement, annotation,
|
var ctx = await buildOrmContext({}, element as ClassElement, annotation,
|
||||||
buildStep, resolver, autoSnakeCaseNames != false);
|
buildStep, resolver, autoSnakeCaseNames != false);
|
||||||
var lib = generateMigrationLibrary(
|
var lib = generateMigrationLibrary(
|
||||||
ctx, element as ClassElement, resolver, buildStep);
|
ctx, element as ClassElement, resolver, buildStep);
|
||||||
if (lib == null) return null;
|
if (lib == null) return null;
|
||||||
return new DartFormatter().format(lib.accept(new DartEmitter()).toString());
|
return DartFormatter().format(lib.accept(DartEmitter()).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
Library generateMigrationLibrary(OrmBuildContext ctx, ClassElement element,
|
Library generateMigrationLibrary(OrmBuildContext ctx, ClassElement element,
|
||||||
Resolver resolver, BuildStep buildStep) {
|
Resolver resolver, BuildStep buildStep) {
|
||||||
return new Library((lib) {
|
return Library((lib) {
|
||||||
lib.body.add(new Class((clazz) {
|
lib.body.add(Class((clazz) {
|
||||||
clazz
|
clazz
|
||||||
..name = '${ctx.buildContext.modelClassName}Migration'
|
..name = '${ctx.buildContext.modelClassName}Migration'
|
||||||
..extend = refer('Migration')
|
..extend = refer('Migration')
|
||||||
|
@ -64,7 +65,7 @@ class MigrationGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Method buildUpMigration(OrmBuildContext ctx, LibraryBuilder lib) {
|
Method buildUpMigration(OrmBuildContext ctx, LibraryBuilder lib) {
|
||||||
return new Method((meth) {
|
return Method((meth) {
|
||||||
var autoIdAndDateFields = const TypeChecker.fromRuntime(Model)
|
var autoIdAndDateFields = const TypeChecker.fromRuntime(Model)
|
||||||
.isAssignableFromType(ctx.buildContext.clazz.type);
|
.isAssignableFromType(ctx.buildContext.clazz.type);
|
||||||
meth
|
meth
|
||||||
|
@ -72,20 +73,20 @@ class MigrationGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
..annotations.add(refer('override'))
|
..annotations.add(refer('override'))
|
||||||
..requiredParameters.add(_schemaParam);
|
..requiredParameters.add(_schemaParam);
|
||||||
|
|
||||||
//var closure = new Method.closure()..addPositional(parameter('table'));
|
//var closure = Method.closure()..addPositional(parameter('table'));
|
||||||
var closure = new Method((closure) {
|
var closure = Method((closure) {
|
||||||
closure
|
closure
|
||||||
..requiredParameters.add(new Parameter((b) => b..name = 'table'))
|
..requiredParameters.add(Parameter((b) => b..name = 'table'))
|
||||||
..body = new Block((closureBody) {
|
..body = Block((closureBody) {
|
||||||
var table = refer('table');
|
var table = refer('table');
|
||||||
|
|
||||||
List<String> dup = [];
|
List<String> dup = [];
|
||||||
ctx.columns.forEach((name, col) {
|
ctx.columns.forEach((name, col) {
|
||||||
var key = ctx.buildContext.resolveFieldName(name);
|
var key = ctx.buildContext.resolveFieldName(name);
|
||||||
|
|
||||||
if (dup.contains(key))
|
if (dup.contains(key)) {
|
||||||
return;
|
return;
|
||||||
else {
|
} else {
|
||||||
// if (key != 'id' || autoIdAndDateFields == false) {
|
// if (key != 'id' || autoIdAndDateFields == false) {
|
||||||
// // Check for relationships that might duplicate
|
// // Check for relationships that might duplicate
|
||||||
// for (var rName in ctx.relations.keys) {
|
// for (var rName in ctx.relations.keys) {
|
||||||
|
@ -111,15 +112,17 @@ class MigrationGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
List<Expression> positional = [literal(key)];
|
List<Expression> positional = [literal(key)];
|
||||||
Map<String, Expression> named = {};
|
Map<String, Expression> named = {};
|
||||||
|
|
||||||
if (autoIdAndDateFields != false && name == 'id')
|
if (autoIdAndDateFields != false && name == 'id') {
|
||||||
methodName = 'serial';
|
methodName = 'serial';
|
||||||
|
}
|
||||||
|
|
||||||
if (methodName == null) {
|
if (methodName == null) {
|
||||||
switch (col.type) {
|
switch (col.type) {
|
||||||
case ColumnType.varChar:
|
case ColumnType.varChar:
|
||||||
methodName = 'varChar';
|
methodName = 'varChar';
|
||||||
if (col.length != null)
|
if (col.length != null) {
|
||||||
named['length'] = literal(col.length);
|
named['length'] = literal(col.length);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ColumnType.serial:
|
case ColumnType.serial:
|
||||||
methodName = 'serial';
|
methodName = 'serial';
|
||||||
|
@ -196,13 +199,14 @@ class MigrationGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
// Definitely an analyzer issue.
|
// Definitely an analyzer issue.
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
defaultExpr = new CodeExpression(
|
defaultExpr = CodeExpression(
|
||||||
new Code(dartObjectToString(defaultValue)),
|
Code(dartObjectToString(defaultValue)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultExpr != null)
|
if (defaultExpr != null) {
|
||||||
cascade.add(refer('defaultsTo').call([defaultExpr]));
|
cascade.add(refer('defaultsTo').call([defaultExpr]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (col.indexType == IndexType.primaryKey ||
|
if (col.indexType == IndexType.primaryKey ||
|
||||||
|
@ -212,20 +216,20 @@ class MigrationGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
cascade.add(refer('unique').call([]));
|
cascade.add(refer('unique').call([]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (col.isNullable != true)
|
if (col.isNullable != true) {
|
||||||
cascade.add(refer('notNull').call([]));
|
cascade.add(refer('notNull').call([]));
|
||||||
|
}
|
||||||
|
|
||||||
if (cascade.isNotEmpty) {
|
if (cascade.isNotEmpty) {
|
||||||
var b = new StringBuffer()
|
var b = StringBuffer()..writeln(field.accept(DartEmitter()));
|
||||||
..writeln(field.accept(new DartEmitter()));
|
|
||||||
|
|
||||||
for (var ex in cascade) {
|
for (var ex in cascade) {
|
||||||
b
|
b
|
||||||
..write('..')
|
..write('..')
|
||||||
..writeln(ex.accept(new DartEmitter()));
|
..writeln(ex.accept(DartEmitter()));
|
||||||
}
|
}
|
||||||
|
|
||||||
field = new CodeExpression(new Code(b.toString()));
|
field = CodeExpression(Code(b.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
closureBody.addExpression(field);
|
closureBody.addExpression(field);
|
||||||
|
@ -259,15 +263,16 @@ class MigrationGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
|
|
||||||
if (relationship.cascadeOnDelete != false &&
|
if (relationship.cascadeOnDelete != false &&
|
||||||
const [RelationshipType.hasOne, RelationshipType.belongsTo]
|
const [RelationshipType.hasOne, RelationshipType.belongsTo]
|
||||||
.contains(relationship.type))
|
.contains(relationship.type)) {
|
||||||
ref = ref.property('onDeleteCascade').call([]);
|
ref = ref.property('onDeleteCascade').call([]);
|
||||||
|
}
|
||||||
closureBody.addExpression(ref);
|
closureBody.addExpression(ref);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
meth.body = new Block((b) {
|
meth.body = Block((b) {
|
||||||
b.addExpression(_schema.property('create').call([
|
b.addExpression(_schema.property('create').call([
|
||||||
literal(ctx.tableName),
|
literal(ctx.tableName),
|
||||||
closure.closure,
|
closure.closure,
|
||||||
|
@ -277,12 +282,12 @@ class MigrationGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Method buildDownMigration(OrmBuildContext ctx) {
|
Method buildDownMigration(OrmBuildContext ctx) {
|
||||||
return new Method((b) {
|
return Method((b) {
|
||||||
b
|
b
|
||||||
..name = 'down'
|
..name = 'down'
|
||||||
..annotations.add(refer('override'))
|
..annotations.add(refer('override'))
|
||||||
..requiredParameters.add(_schemaParam)
|
..requiredParameters.add(_schemaParam)
|
||||||
..body = new Block((b) {
|
..body = Block((b) {
|
||||||
var named = <String, Expression>{};
|
var named = <String, Expression>{};
|
||||||
|
|
||||||
if (ctx.relations.values.any((r) =>
|
if (ctx.relations.values.any((r) =>
|
||||||
|
|
|
@ -43,7 +43,7 @@ FieldElement findPrimaryFieldInList(
|
||||||
var columnAnnotation = columnTypeChecker.firstAnnotationOf(element);
|
var columnAnnotation = columnTypeChecker.firstAnnotationOf(element);
|
||||||
|
|
||||||
if (columnAnnotation != null) {
|
if (columnAnnotation != null) {
|
||||||
var column = reviveColumn(new ConstantReader(columnAnnotation));
|
var column = reviveColumn(ConstantReader(columnAnnotation));
|
||||||
// print(
|
// print(
|
||||||
// ' * Found column on ${field.name} with indexType = ${column.indexType}');
|
// ' * Found column on ${field.name} with indexType = ${column.indexType}');
|
||||||
// print(element.metadata);
|
// print(element.metadata);
|
||||||
|
@ -58,15 +58,14 @@ FieldElement findPrimaryFieldInList(
|
||||||
return specialId;
|
return specialId;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, OrmBuildContext> _cache = {};
|
|
||||||
|
|
||||||
Future<OrmBuildContext> buildOrmContext(
|
Future<OrmBuildContext> buildOrmContext(
|
||||||
|
Map<String, OrmBuildContext> cache,
|
||||||
ClassElement clazz,
|
ClassElement clazz,
|
||||||
ConstantReader annotation,
|
ConstantReader annotation,
|
||||||
BuildStep buildStep,
|
BuildStep buildStep,
|
||||||
Resolver resolver,
|
Resolver resolver,
|
||||||
bool autoSnakeCaseNames,
|
bool autoSnakeCaseNames,
|
||||||
{bool heedExclude: true}) async {
|
{bool heedExclude = true}) async {
|
||||||
// Check for @generatedSerializable
|
// Check for @generatedSerializable
|
||||||
// ignore: unused_local_variable
|
// ignore: unused_local_variable
|
||||||
DartObject generatedSerializable;
|
DartObject generatedSerializable;
|
||||||
|
@ -79,8 +78,8 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
}
|
}
|
||||||
|
|
||||||
var id = clazz.location.components.join('-');
|
var id = clazz.location.components.join('-');
|
||||||
if (_cache.containsKey(id)) {
|
if (cache.containsKey(id)) {
|
||||||
return _cache[id];
|
return cache[id];
|
||||||
}
|
}
|
||||||
var buildCtx = await buildContext(
|
var buildCtx = await buildContext(
|
||||||
clazz, annotation, buildStep, resolver, autoSnakeCaseNames,
|
clazz, annotation, buildStep, resolver, autoSnakeCaseNames,
|
||||||
|
@ -88,13 +87,13 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
var ormAnnotation = reviveORMAnnotation(annotation);
|
var ormAnnotation = reviveORMAnnotation(annotation);
|
||||||
// print(
|
// print(
|
||||||
// 'tableName (${annotation.objectValue.type.name}) => ${ormAnnotation.tableName} from ${clazz.name} (${annotation.revive().namedArguments})');
|
// 'tableName (${annotation.objectValue.type.name}) => ${ormAnnotation.tableName} from ${clazz.name} (${annotation.revive().namedArguments})');
|
||||||
var ctx = new OrmBuildContext(
|
var ctx = OrmBuildContext(
|
||||||
buildCtx,
|
buildCtx,
|
||||||
ormAnnotation,
|
ormAnnotation,
|
||||||
(ormAnnotation.tableName?.isNotEmpty == true)
|
(ormAnnotation.tableName?.isNotEmpty == true)
|
||||||
? ormAnnotation.tableName
|
? ormAnnotation.tableName
|
||||||
: pluralize(new ReCase(clazz.name).snakeCase));
|
: pluralize(ReCase(clazz.name).snakeCase));
|
||||||
_cache[id] = ctx;
|
cache[id] = ctx;
|
||||||
|
|
||||||
// Read all fields
|
// Read all fields
|
||||||
for (var field in buildCtx.fields) {
|
for (var field in buildCtx.fields) {
|
||||||
|
@ -105,7 +104,7 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
// print('${element.name} => $columnAnnotation');
|
// print('${element.name} => $columnAnnotation');
|
||||||
|
|
||||||
if (columnAnnotation != null) {
|
if (columnAnnotation != null) {
|
||||||
column = reviveColumn(new ConstantReader(columnAnnotation));
|
column = reviveColumn(ConstantReader(columnAnnotation));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (column == null && isSpecialId(ctx, field)) {
|
if (column == null && isSpecialId(ctx, field)) {
|
||||||
|
@ -117,7 +116,7 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
|
|
||||||
if (column == null) {
|
if (column == null) {
|
||||||
// Guess what kind of column this is...
|
// Guess what kind of column this is...
|
||||||
column = new Column(
|
column = Column(
|
||||||
type: inferColumnType(
|
type: inferColumnType(
|
||||||
buildCtx.resolveSerializedFieldType(field.name),
|
buildCtx.resolveSerializedFieldType(field.name),
|
||||||
),
|
),
|
||||||
|
@ -125,7 +124,7 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (column != null && column.type == null) {
|
if (column != null && column.type == null) {
|
||||||
column = new Column(
|
column = Column(
|
||||||
isNullable: column.isNullable,
|
isNullable: column.isNullable,
|
||||||
length: column.length,
|
length: column.length,
|
||||||
indexType: column.indexType,
|
indexType: column.indexType,
|
||||||
|
@ -139,7 +138,7 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
var ann = relationshipTypeChecker.firstAnnotationOf(el);
|
var ann = relationshipTypeChecker.firstAnnotationOf(el);
|
||||||
|
|
||||||
if (ann != null) {
|
if (ann != null) {
|
||||||
var cr = new ConstantReader(ann);
|
var cr = ConstantReader(ann);
|
||||||
var rc = ctx.buildContext.modelClassNameRecase;
|
var rc = ctx.buildContext.modelClassNameRecase;
|
||||||
var type = cr.read('type').intValue;
|
var type = cr.read('type').intValue;
|
||||||
var localKey = cr.peek('localKey')?.stringValue;
|
var localKey = cr.peek('localKey')?.stringValue;
|
||||||
|
@ -157,7 +156,7 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
isListOfModelType(field.type as InterfaceType)) ||
|
isListOfModelType(field.type as InterfaceType)) ||
|
||||||
isModelClass(field.type);
|
isModelClass(field.type);
|
||||||
if (!canUse) {
|
if (!canUse) {
|
||||||
throw new UnsupportedError(
|
throw UnsupportedError(
|
||||||
'Cannot apply relationship to field "${field.name}" - ${field.type} is not assignable to Model.');
|
'Cannot apply relationship to field "${field.name}" - ${field.type} is not assignable to Model.');
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
@ -173,8 +172,9 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
var modelType = firstModelAncestor(refType) ?? refType;
|
var modelType = firstModelAncestor(refType) ?? refType;
|
||||||
|
|
||||||
foreign = await buildOrmContext(
|
foreign = await buildOrmContext(
|
||||||
|
cache,
|
||||||
modelType.element as ClassElement,
|
modelType.element as ClassElement,
|
||||||
new ConstantReader(const TypeChecker.fromRuntime(Orm)
|
ConstantReader(const TypeChecker.fromRuntime(Orm)
|
||||||
.firstAnnotationOf(modelType.element)),
|
.firstAnnotationOf(modelType.element)),
|
||||||
buildStep,
|
buildStep,
|
||||||
resolver,
|
resolver,
|
||||||
|
@ -183,8 +183,9 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
// Resolve throughType as well
|
// Resolve throughType as well
|
||||||
if (through != null && through is InterfaceType) {
|
if (through != null && through is InterfaceType) {
|
||||||
throughContext = await buildOrmContext(
|
throughContext = await buildOrmContext(
|
||||||
|
cache,
|
||||||
through.element,
|
through.element,
|
||||||
new ConstantReader(const TypeChecker.fromRuntime(Serializable)
|
ConstantReader(const TypeChecker.fromRuntime(Serializable)
|
||||||
.firstAnnotationOf(modelType.element)),
|
.firstAnnotationOf(modelType.element)),
|
||||||
buildStep,
|
buildStep,
|
||||||
resolver,
|
resolver,
|
||||||
|
@ -196,20 +197,20 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
|
|
||||||
if (ormAnn != null) {
|
if (ormAnn != null) {
|
||||||
foreignTable =
|
foreignTable =
|
||||||
new ConstantReader(ormAnn).peek('tableName')?.stringValue;
|
ConstantReader(ormAnn).peek('tableName')?.stringValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreignTable ??=
|
foreignTable ??=
|
||||||
pluralize(foreign.buildContext.modelClassNameRecase.snakeCase);
|
pluralize(foreign.buildContext.modelClassNameRecase.snakeCase);
|
||||||
} on StackOverflowError {
|
} on StackOverflowError {
|
||||||
throw new UnsupportedError(
|
throw UnsupportedError(
|
||||||
'There is an infinite cycle between ${clazz.name} and ${field.type.name}. This triggered a stack overflow.');
|
'There is an infinite cycle between ${clazz.name} and ${field.type.name}. This triggered a stack overflow.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in missing keys
|
// Fill in missing keys
|
||||||
var rcc = new ReCase(field.name);
|
var rcc = ReCase(field.name);
|
||||||
|
|
||||||
String keyName(OrmBuildContext ctx, String missing) {
|
String keyName(OrmBuildContext ctx, String missing) {
|
||||||
var _keyName =
|
var _keyName =
|
||||||
|
@ -236,7 +237,25 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
localKey ??= '${rcc.snakeCase}_$foreignKey';
|
localKey ??= '${rcc.snakeCase}_$foreignKey';
|
||||||
}
|
}
|
||||||
|
|
||||||
var relation = new RelationshipReader(
|
// Figure out the join type.
|
||||||
|
var joinType = JoinType.left;
|
||||||
|
var joinTypeRdr = cr.peek('joinType')?.objectValue;
|
||||||
|
if (joinTypeRdr != null) {
|
||||||
|
// Unfortunately, the analyzer library provides little to nothing
|
||||||
|
// in the way of reading enums from source, so here's a hack.
|
||||||
|
var joinTypeType = (joinTypeRdr.type as InterfaceType);
|
||||||
|
var enumFields =
|
||||||
|
joinTypeType.element.fields.where((f) => f.isEnumConstant).toList();
|
||||||
|
|
||||||
|
for (int i = 0; i < enumFields.length; i++) {
|
||||||
|
if (enumFields[i].constantValue == joinTypeRdr) {
|
||||||
|
joinType = JoinType.values[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var relation = RelationshipReader(
|
||||||
type,
|
type,
|
||||||
localKey: localKey,
|
localKey: localKey,
|
||||||
foreignKey: foreignKey,
|
foreignKey: foreignKey,
|
||||||
|
@ -245,34 +264,38 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
through: through,
|
through: through,
|
||||||
foreign: foreign,
|
foreign: foreign,
|
||||||
throughContext: throughContext,
|
throughContext: throughContext,
|
||||||
|
joinType: joinType,
|
||||||
);
|
);
|
||||||
|
|
||||||
// print('Relation on ${buildCtx.originalClassName}.${field.name} => '
|
// print('Relation on ${buildCtx.originalClassName}.${field.name} => '
|
||||||
// 'foreignKey=$foreignKey, localKey=$localKey');
|
// 'foreignKey=$foreignKey, localKey=$localKey');
|
||||||
|
|
||||||
if (relation.type == RelationshipType.belongsTo) {
|
if (relation.type == RelationshipType.belongsTo) {
|
||||||
var name = new ReCase(relation.localKey).camelCase;
|
var name = ReCase(relation.localKey).camelCase;
|
||||||
ctx.buildContext.aliases[name] = relation.localKey;
|
ctx.buildContext.aliases[name] = relation.localKey;
|
||||||
|
|
||||||
if (!ctx.effectiveFields.any((f) => f.name == field.name)) {
|
if (!ctx.effectiveFields.any((f) => f.name == field.name)) {
|
||||||
var foreignField = relation.findForeignField(ctx);
|
var foreignField = relation.findForeignField(ctx);
|
||||||
var foreign = relation.throughContext ?? relation.foreign;
|
var foreign = relation.throughContext ?? relation.foreign;
|
||||||
var type = foreignField.type;
|
var type = foreignField.type;
|
||||||
if (isSpecialId(foreign, foreignField))
|
if (isSpecialId(foreign, foreignField)) {
|
||||||
type = field.type.element.context.typeProvider.intType;
|
type = field.type.element.context.typeProvider.intType;
|
||||||
var rf = new RelationFieldImpl(name, relation, type, field);
|
}
|
||||||
|
var rf = RelationFieldImpl(name, relation, type, field);
|
||||||
ctx.effectiveFields.add(rf);
|
ctx.effectiveFields.add(rf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.relations[field.name] = relation;
|
ctx.relations[field.name] = relation;
|
||||||
} else {
|
} else {
|
||||||
if (column?.type == null)
|
if (column?.type == null) {
|
||||||
throw 'Cannot infer SQL column type for field "${ctx.buildContext.originalClassName}.${field.name}" with type "${field.type.displayName}".';
|
throw 'Cannot infer SQL column type for field "${ctx.buildContext.originalClassName}.${field.name}" with type "${field.type.displayName}".';
|
||||||
|
}
|
||||||
ctx.columns[field.name] = column;
|
ctx.columns[field.name] = column;
|
||||||
|
|
||||||
if (!ctx.effectiveFields.any((f) => f.name == field.name))
|
if (!ctx.effectiveFields.any((f) => f.name == field.name)) {
|
||||||
ctx.effectiveFields.add(field);
|
ctx.effectiveFields.add(field);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,22 +303,30 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnType inferColumnType(DartType type) {
|
ColumnType inferColumnType(DartType type) {
|
||||||
if (const TypeChecker.fromRuntime(String).isAssignableFromType(type))
|
if (const TypeChecker.fromRuntime(String).isAssignableFromType(type)) {
|
||||||
return ColumnType.varChar;
|
return ColumnType.varChar;
|
||||||
if (const TypeChecker.fromRuntime(int).isAssignableFromType(type))
|
}
|
||||||
|
if (const TypeChecker.fromRuntime(int).isAssignableFromType(type)) {
|
||||||
return ColumnType.int;
|
return ColumnType.int;
|
||||||
if (const TypeChecker.fromRuntime(double).isAssignableFromType(type))
|
}
|
||||||
|
if (const TypeChecker.fromRuntime(double).isAssignableFromType(type)) {
|
||||||
return ColumnType.decimal;
|
return ColumnType.decimal;
|
||||||
if (const TypeChecker.fromRuntime(num).isAssignableFromType(type))
|
}
|
||||||
|
if (const TypeChecker.fromRuntime(num).isAssignableFromType(type)) {
|
||||||
return ColumnType.numeric;
|
return ColumnType.numeric;
|
||||||
if (const TypeChecker.fromRuntime(bool).isAssignableFromType(type))
|
}
|
||||||
|
if (const TypeChecker.fromRuntime(bool).isAssignableFromType(type)) {
|
||||||
return ColumnType.boolean;
|
return ColumnType.boolean;
|
||||||
if (const TypeChecker.fromRuntime(DateTime).isAssignableFromType(type))
|
}
|
||||||
|
if (const TypeChecker.fromRuntime(DateTime).isAssignableFromType(type)) {
|
||||||
return ColumnType.timeStamp;
|
return ColumnType.timeStamp;
|
||||||
if (const TypeChecker.fromRuntime(Map).isAssignableFromType(type))
|
}
|
||||||
|
if (const TypeChecker.fromRuntime(Map).isAssignableFromType(type)) {
|
||||||
return ColumnType.jsonb;
|
return ColumnType.jsonb;
|
||||||
if (const TypeChecker.fromRuntime(List).isAssignableFromType(type))
|
}
|
||||||
|
if (const TypeChecker.fromRuntime(List).isAssignableFromType(type)) {
|
||||||
return ColumnType.jsonb;
|
return ColumnType.jsonb;
|
||||||
|
}
|
||||||
if (type is InterfaceType && type.element.isEnum) return ColumnType.int;
|
if (type is InterfaceType && type.element.isEnum) return ColumnType.int;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -317,10 +348,10 @@ Column reviveColumn(ConstantReader cr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (columnObj != null) {
|
if (columnObj != null) {
|
||||||
columnType = new _ColumnType(columnObj);
|
columnType = _ColumnType(columnObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Column(
|
return Column(
|
||||||
isNullable: cr.peek('isNullable')?.boolValue,
|
isNullable: cr.peek('isNullable')?.boolValue,
|
||||||
length: cr.peek('length')?.intValue,
|
length: cr.peek('length')?.intValue,
|
||||||
type: columnType,
|
type: columnType,
|
||||||
|
@ -329,7 +360,7 @@ Column reviveColumn(ConstantReader cr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const TypeChecker relationshipTypeChecker =
|
const TypeChecker relationshipTypeChecker =
|
||||||
const TypeChecker.fromRuntime(Relationship);
|
TypeChecker.fromRuntime(Relationship);
|
||||||
|
|
||||||
class OrmBuildContext {
|
class OrmBuildContext {
|
||||||
final BuildContext buildContext;
|
final BuildContext buildContext;
|
||||||
|
|
|
@ -17,14 +17,14 @@ var floatTypes = [
|
||||||
];
|
];
|
||||||
|
|
||||||
Builder ormBuilder(BuilderOptions options) {
|
Builder ormBuilder(BuilderOptions options) {
|
||||||
return new SharedPartBuilder([
|
return SharedPartBuilder([
|
||||||
new OrmGenerator(
|
OrmGenerator(
|
||||||
autoSnakeCaseNames: options.config['auto_snake_case_names'] != false)
|
autoSnakeCaseNames: options.config['auto_snake_case_names'] != false)
|
||||||
], 'angel_orm');
|
], 'angel_orm');
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeReference futureOf(String type) {
|
TypeReference futureOf(String type) {
|
||||||
return new TypeReference((b) => b
|
return TypeReference((b) => b
|
||||||
..symbol = 'Future'
|
..symbol = 'Future'
|
||||||
..types.add(refer(type)));
|
..types.add(refer(type)));
|
||||||
}
|
}
|
||||||
|
@ -39,17 +39,17 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
Future<String> generateForAnnotatedElement(
|
Future<String> generateForAnnotatedElement(
|
||||||
Element element, ConstantReader annotation, BuildStep buildStep) async {
|
Element element, ConstantReader annotation, BuildStep buildStep) async {
|
||||||
if (element is ClassElement) {
|
if (element is ClassElement) {
|
||||||
var ctx = await buildOrmContext(element, annotation, buildStep,
|
var ctx = await buildOrmContext({}, element, annotation, buildStep,
|
||||||
buildStep.resolver, autoSnakeCaseNames);
|
buildStep.resolver, autoSnakeCaseNames);
|
||||||
var lib = buildOrmLibrary(buildStep.inputId, ctx);
|
var lib = buildOrmLibrary(buildStep.inputId, ctx);
|
||||||
return lib.accept(new DartEmitter()).toString();
|
return lib.accept(DartEmitter()).toString();
|
||||||
} else {
|
} else {
|
||||||
throw 'The @Orm() annotation can only be applied to classes.';
|
throw 'The @Orm() annotation can only be applied to classes.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Library buildOrmLibrary(AssetId inputId, OrmBuildContext ctx) {
|
Library buildOrmLibrary(AssetId inputId, OrmBuildContext ctx) {
|
||||||
return new Library((lib) {
|
return Library((lib) {
|
||||||
// Create `FooQuery` class
|
// Create `FooQuery` class
|
||||||
// Create `FooQueryWhere` class
|
// Create `FooQueryWhere` class
|
||||||
lib.body.add(buildQueryClass(ctx));
|
lib.body.add(buildQueryClass(ctx));
|
||||||
|
@ -59,12 +59,12 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Class buildQueryClass(OrmBuildContext ctx) {
|
Class buildQueryClass(OrmBuildContext ctx) {
|
||||||
return new Class((clazz) {
|
return Class((clazz) {
|
||||||
var rc = ctx.buildContext.modelClassNameRecase;
|
var rc = ctx.buildContext.modelClassNameRecase;
|
||||||
var queryWhereType = refer('${rc.pascalCase}QueryWhere');
|
var queryWhereType = refer('${rc.pascalCase}QueryWhere');
|
||||||
clazz
|
clazz
|
||||||
..name = '${rc.pascalCase}Query'
|
..name = '${rc.pascalCase}Query'
|
||||||
..extend = new TypeReference((b) {
|
..extend = TypeReference((b) {
|
||||||
b
|
b
|
||||||
..symbol = 'Query'
|
..symbol = 'Query'
|
||||||
..types.addAll([
|
..types.addAll([
|
||||||
|
@ -96,7 +96,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Add values
|
// Add values
|
||||||
clazz.fields.add(new Field((b) {
|
clazz.fields.add(Field((b) {
|
||||||
var type = refer('${rc.pascalCase}QueryValues');
|
var type = refer('${rc.pascalCase}QueryValues');
|
||||||
b
|
b
|
||||||
..name = 'values'
|
..name = 'values'
|
||||||
|
@ -107,23 +107,23 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Add tableName
|
// Add tableName
|
||||||
clazz.methods.add(new Method((m) {
|
clazz.methods.add(Method((m) {
|
||||||
m
|
m
|
||||||
..name = 'tableName'
|
..name = 'tableName'
|
||||||
..annotations.add(refer('override'))
|
..annotations.add(refer('override'))
|
||||||
..type = MethodType.getter
|
..type = MethodType.getter
|
||||||
..body = new Block((b) {
|
..body = Block((b) {
|
||||||
b.addExpression(literalString(ctx.tableName).returned);
|
b.addExpression(literalString(ctx.tableName).returned);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Add fields getter
|
// Add fields getter
|
||||||
clazz.methods.add(new Method((m) {
|
clazz.methods.add(Method((m) {
|
||||||
m
|
m
|
||||||
..name = 'fields'
|
..name = 'fields'
|
||||||
..annotations.add(refer('override'))
|
..annotations.add(refer('override'))
|
||||||
..type = MethodType.getter
|
..type = MethodType.getter
|
||||||
..body = new Block((b) {
|
..body = Block((b) {
|
||||||
var names = ctx.effectiveFields
|
var names = ctx.effectiveFields
|
||||||
.map((f) =>
|
.map((f) =>
|
||||||
literalString(ctx.buildContext.resolveFieldName(f.name)))
|
literalString(ctx.buildContext.resolveFieldName(f.name)))
|
||||||
|
@ -133,41 +133,41 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Add _where member
|
// Add _where member
|
||||||
clazz.fields.add(new Field((b) {
|
clazz.fields.add(Field((b) {
|
||||||
b
|
b
|
||||||
..name = '_where'
|
..name = '_where'
|
||||||
..type = queryWhereType;
|
..type = queryWhereType;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Add where getter
|
// Add where getter
|
||||||
clazz.methods.add(new Method((b) {
|
clazz.methods.add(Method((b) {
|
||||||
b
|
b
|
||||||
..name = 'where'
|
..name = 'where'
|
||||||
..type = MethodType.getter
|
..type = MethodType.getter
|
||||||
..returns = queryWhereType
|
..returns = queryWhereType
|
||||||
..annotations.add(refer('override'))
|
..annotations.add(refer('override'))
|
||||||
..body = new Block((b) => b.addExpression(refer('_where').returned));
|
..body = Block((b) => b.addExpression(refer('_where').returned));
|
||||||
}));
|
}));
|
||||||
// newWhereClause()
|
// newWhereClause()
|
||||||
clazz.methods.add(new Method((b) {
|
clazz.methods.add(Method((b) {
|
||||||
b
|
b
|
||||||
..name = 'newWhereClause'
|
..name = 'newWhereClause'
|
||||||
..annotations.add(refer('override'))
|
..annotations.add(refer('override'))
|
||||||
..returns = queryWhereType
|
..returns = queryWhereType
|
||||||
..body = new Block((b) => b.addExpression(
|
..body = Block((b) => b.addExpression(
|
||||||
queryWhereType.newInstance([refer('this')]).returned));
|
queryWhereType.newInstance([refer('this')]).returned));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Add deserialize()
|
// Add deserialize()
|
||||||
clazz.methods.add(new Method((m) {
|
clazz.methods.add(Method((m) {
|
||||||
m
|
m
|
||||||
..name = 'parseRow'
|
..name = 'parseRow'
|
||||||
..static = true
|
..static = true
|
||||||
..returns = ctx.buildContext.modelClassType
|
..returns = ctx.buildContext.modelClassType
|
||||||
..requiredParameters.add(new Parameter((b) => b
|
..requiredParameters.add(Parameter((b) => b
|
||||||
..name = 'row'
|
..name = 'row'
|
||||||
..type = refer('List')))
|
..type = refer('List')))
|
||||||
..body = new Block((b) {
|
..body = Block((b) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
var args = <String, Expression>{};
|
var args = <String, Expression>{};
|
||||||
|
|
||||||
|
@ -177,11 +177,11 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
if (isSpecialId(ctx, field)) type = refer('int');
|
if (isSpecialId(ctx, field)) type = refer('int');
|
||||||
|
|
||||||
var expr = (refer('row').index(literalNum(i++)));
|
var expr = (refer('row').index(literalNum(i++)));
|
||||||
if (isSpecialId(ctx, field))
|
if (isSpecialId(ctx, field)) {
|
||||||
expr = expr.property('toString').call([]);
|
expr = expr.property('toString').call([]);
|
||||||
else if (field is RelationFieldImpl)
|
} else if (field is RelationFieldImpl) {
|
||||||
continue;
|
continue;
|
||||||
else if (ctx.columns[field.name]?.type == ColumnType.json) {
|
} else if (ctx.columns[field.name]?.type == ColumnType.json) {
|
||||||
expr = refer('json')
|
expr = refer('json')
|
||||||
.property('decode')
|
.property('decode')
|
||||||
.call([expr.asA(refer('String'))]).asA(type);
|
.call([expr.asA(refer('String'))]).asA(type);
|
||||||
|
@ -193,14 +193,15 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
var isNull = expr.equalTo(literalNull);
|
var isNull = expr.equalTo(literalNull);
|
||||||
expr = isNull.conditional(literalNull,
|
expr = isNull.conditional(literalNull,
|
||||||
type.property('values').index(expr.asA(refer('int'))));
|
type.property('values').index(expr.asA(refer('int'))));
|
||||||
} else
|
} else {
|
||||||
expr = expr.asA(type);
|
expr = expr.asA(type);
|
||||||
|
}
|
||||||
|
|
||||||
args[field.name] = expr;
|
args[field.name] = expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
b.statements
|
b.statements
|
||||||
.add(new Code('if (row.every((x) => x == null)) return null;'));
|
.add(Code('if (row.every((x) => x == null)) return null;'));
|
||||||
b.addExpression(ctx.buildContext.modelClassType
|
b.addExpression(ctx.buildContext.modelClassType
|
||||||
.newInstance([], args).assignVar('model'));
|
.newInstance([], args).assignVar('model'));
|
||||||
|
|
||||||
|
@ -230,11 +231,11 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}
|
}
|
||||||
var expr =
|
var expr =
|
||||||
refer('model').property('copyWith').call([], {name: parsed});
|
refer('model').property('copyWith').call([], {name: parsed});
|
||||||
var block = new Block(
|
var block =
|
||||||
(b) => b.addExpression(refer('model').assign(expr)));
|
Block((b) => b.addExpression(refer('model').assign(expr)));
|
||||||
var blockStr = block.accept(new DartEmitter());
|
var blockStr = block.accept(DartEmitter());
|
||||||
var ifStr = 'if (row.length > $i) { $blockStr }';
|
var ifStr = 'if (row.length > $i) { $blockStr }';
|
||||||
b.statements.add(new Code(ifStr));
|
b.statements.add(Code(ifStr));
|
||||||
i += relation.foreign.effectiveFields.length;
|
i += relation.foreign.effectiveFields.length;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -242,28 +243,33 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
clazz.methods.add(new Method((m) {
|
clazz.methods.add(Method((m) {
|
||||||
m
|
m
|
||||||
..name = 'deserialize'
|
..name = 'deserialize'
|
||||||
..annotations.add(refer('override'))
|
..annotations.add(refer('override'))
|
||||||
..requiredParameters.add(new Parameter((b) => b
|
..requiredParameters.add(Parameter((b) => b
|
||||||
..name = 'row'
|
..name = 'row'
|
||||||
..type = refer('List')))
|
..type = refer('List')))
|
||||||
..body = new Block((b) {
|
..body = Block((b) {
|
||||||
b.addExpression(refer('parseRow').call([refer('row')]).returned);
|
b.addExpression(refer('parseRow').call([refer('row')]).returned);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// If there are any relations, we need some overrides.
|
// If there are any relations, we need some overrides.
|
||||||
clazz.constructors.add(new Constructor((b) {
|
clazz.constructors.add(Constructor((b) {
|
||||||
b
|
b
|
||||||
|
..optionalParameters.add(Parameter((b) => b
|
||||||
|
..named = true
|
||||||
|
..name = 'parent'
|
||||||
|
..type = refer('Query')))
|
||||||
..optionalParameters.add(Parameter((b) => b
|
..optionalParameters.add(Parameter((b) => b
|
||||||
..named = true
|
..named = true
|
||||||
..name = 'trampoline'
|
..name = 'trampoline'
|
||||||
..type = TypeReference((b) => b
|
..type = TypeReference((b) => b
|
||||||
..symbol = 'Set'
|
..symbol = 'Set'
|
||||||
..types.add(refer('String')))))
|
..types.add(refer('String')))))
|
||||||
..body = new Block((b) {
|
..initializers.add(Code('super(parent: parent)'))
|
||||||
|
..body = Block((b) {
|
||||||
b.statements.addAll([
|
b.statements.addAll([
|
||||||
Code('trampoline ??= Set();'),
|
Code('trampoline ??= Set();'),
|
||||||
Code('trampoline.add(tableName);'),
|
Code('trampoline.add(tableName);'),
|
||||||
|
@ -275,6 +281,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
.assign(queryWhereType.newInstance([refer('this')])),
|
.assign(queryWhereType.newInstance([refer('this')])),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Note: this is where subquery fields for relations are added.
|
||||||
ctx.relations.forEach((fieldName, relation) {
|
ctx.relations.forEach((fieldName, relation) {
|
||||||
//var name = ctx.buildContext.resolveFieldName(fieldName);
|
//var name = ctx.buildContext.resolveFieldName(fieldName);
|
||||||
if (relation.type == RelationshipType.belongsTo ||
|
if (relation.type == RelationshipType.belongsTo ||
|
||||||
|
@ -283,29 +290,106 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
var foreign = relation.throughContext ?? relation.foreign;
|
var foreign = relation.throughContext ?? relation.foreign;
|
||||||
|
|
||||||
// If this is a many-to-many, add the fields from the other object.
|
// If this is a many-to-many, add the fields from the other object.
|
||||||
var additionalFields = relation.foreign.effectiveFields
|
|
||||||
// .where((f) => f.name != 'id' || !isSpecialId(ctx, f))
|
var additionalStrs = relation.foreign.effectiveFields.map((f) =>
|
||||||
.map((f) => literalString(relation.foreign.buildContext
|
relation.foreign.buildContext.resolveFieldName(f.name));
|
||||||
.resolveFieldName(f.name)));
|
var additionalFields = additionalStrs.map(literalString);
|
||||||
|
|
||||||
var joinArgs = [relation.localKey, relation.foreignKey]
|
var joinArgs = [relation.localKey, relation.foreignKey]
|
||||||
.map(literalString)
|
.map(literalString)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
// Instead of passing the table as-is, we'll compile a subquery.
|
// In the case of a many-to-many, we don't generate a subquery field,
|
||||||
if (relation.type == RelationshipType.hasMany) {
|
// as it easily leads to stack overflows.
|
||||||
var foreignQueryType =
|
if (relation.isManyToMany) {
|
||||||
foreign.buildContext.modelClassNameRecase.pascalCase +
|
// We can't simply join against the "through" table; this itself must
|
||||||
'Query';
|
// be a join.
|
||||||
joinArgs.insert(
|
// (SELECT role_users.role_id, <user_fields>
|
||||||
0,
|
// FROM users
|
||||||
refer(foreignQueryType).newInstance(
|
// LEFT JOIN role_users ON role_users.user_id=users.id)
|
||||||
[], {'trampoline': refer('trampoline')}));
|
var foreignFields = additionalStrs
|
||||||
|
.map((f) => '${relation.foreign.tableName}.$f');
|
||||||
|
var b = StringBuffer('(SELECT ');
|
||||||
|
// role_users.role_id
|
||||||
|
b.write('${relation.throughContext.tableName}');
|
||||||
|
b.write('.${relation.foreignKey}');
|
||||||
|
// , <user_fields>
|
||||||
|
b.write(foreignFields.isEmpty
|
||||||
|
? ''
|
||||||
|
: ', ' + foreignFields.join(', '));
|
||||||
|
// FROM users
|
||||||
|
b.write(' FROM ');
|
||||||
|
b.write(relation.foreign.tableName);
|
||||||
|
// LEFT JOIN role_users
|
||||||
|
b.write(' LEFT JOIN ${relation.throughContext.tableName}');
|
||||||
|
// Figure out which field on the "through" table points to users (foreign).
|
||||||
|
var throughRelation =
|
||||||
|
relation.throughContext.relations.values.firstWhere((e) {
|
||||||
|
return e.foreignTable == relation.foreign.tableName;
|
||||||
|
}, orElse: () {
|
||||||
|
// _Role has a many-to-many to _User through _RoleUser, but
|
||||||
|
// _RoleUser has no relation pointing to _User.
|
||||||
|
var b = StringBuffer();
|
||||||
|
b.write(ctx.buildContext.modelClassName);
|
||||||
|
b.write('has a many-to-many relationship to ');
|
||||||
|
b.write(relation.foreign.buildContext.modelClassName);
|
||||||
|
b.write(' through ');
|
||||||
|
b.write(
|
||||||
|
relation.throughContext.buildContext.modelClassName);
|
||||||
|
b.write(', but ');
|
||||||
|
b.write(
|
||||||
|
relation.throughContext.buildContext.modelClassName);
|
||||||
|
b.write('has no relation pointing to ');
|
||||||
|
b.write(relation.foreign.buildContext.modelClassName);
|
||||||
|
b.write('.');
|
||||||
|
throw b.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ON role_users.user_id=users.id)
|
||||||
|
b.write(' ON ');
|
||||||
|
b.write('${relation.throughContext.tableName}');
|
||||||
|
b.write('.');
|
||||||
|
b.write(throughRelation.localKey);
|
||||||
|
b.write('=');
|
||||||
|
b.write(relation.foreign.tableName);
|
||||||
|
b.write('.');
|
||||||
|
b.write(throughRelation.foreignKey);
|
||||||
|
b.write(')');
|
||||||
|
|
||||||
|
joinArgs.insert(0, literalString(b.toString()));
|
||||||
} else {
|
} else {
|
||||||
joinArgs.insert(0, literalString(foreign.tableName));
|
// In the past, we would either do a join on the table name
|
||||||
|
// itself, or create an instance of a query.
|
||||||
|
//
|
||||||
|
// From this point on, however, we will create a field for each
|
||||||
|
// join, so that users can customize the generated query.
|
||||||
|
//
|
||||||
|
// There'll be a private `_field`, and then a getter, named `field`,
|
||||||
|
// that returns the subquery object.
|
||||||
|
var foreignQueryType = refer(
|
||||||
|
foreign.buildContext.modelClassNameRecase.pascalCase +
|
||||||
|
'Query');
|
||||||
|
clazz
|
||||||
|
..fields.add(Field((b) => b
|
||||||
|
..name = '_$fieldName'
|
||||||
|
..type = foreignQueryType))
|
||||||
|
..methods.add(Method((b) => b
|
||||||
|
..name = fieldName
|
||||||
|
..type = MethodType.getter
|
||||||
|
..returns = foreignQueryType
|
||||||
|
..body = refer('_$fieldName').returned.statement));
|
||||||
|
|
||||||
|
// Assign a value to `_field`.
|
||||||
|
var queryInstantiation = foreignQueryType.newInstance([], {
|
||||||
|
'trampoline': refer('trampoline'),
|
||||||
|
'parent': refer('this')
|
||||||
|
});
|
||||||
|
joinArgs.insert(
|
||||||
|
0, refer('_$fieldName').assign(queryInstantiation));
|
||||||
}
|
}
|
||||||
|
|
||||||
b.addExpression(refer('leftJoin').call(joinArgs, {
|
var joinType = relation.joinTypeString;
|
||||||
|
b.addExpression(refer(joinType).call(joinArgs, {
|
||||||
'additionalFields':
|
'additionalFields':
|
||||||
literalConstList(additionalFields.toList()),
|
literalConstList(additionalFields.toList()),
|
||||||
'trampoline': refer('trampoline'),
|
'trampoline': refer('trampoline'),
|
||||||
|
@ -331,12 +415,11 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
});
|
});
|
||||||
var out = outExprs.reduce((a, b) => a.and(b));
|
var out = outExprs.reduce((a, b) => a.and(b));
|
||||||
|
|
||||||
clazz.methods.add(new Method((b) {
|
clazz.methods.add(Method((b) {
|
||||||
b
|
b
|
||||||
..name = 'canCompile'
|
..name = 'canCompile'
|
||||||
..annotations.add(refer('override'))
|
..annotations.add(refer('override'))
|
||||||
..requiredParameters
|
..requiredParameters.add(Parameter((b) => b..name = 'trampoline'))
|
||||||
.add(new Parameter((b) => b..name = 'trampoline'))
|
|
||||||
..returns = refer('bool')
|
..returns = refer('bool')
|
||||||
..body = Block((b) {
|
..body = Block((b) {
|
||||||
b.addExpression(out.returned);
|
b.addExpression(out.returned);
|
||||||
|
@ -344,134 +427,16 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Ultimately remove the insert override
|
|
||||||
if (false && ctx.relations.isNotEmpty) {
|
|
||||||
clazz.methods.add(new Method((b) {
|
|
||||||
b
|
|
||||||
..name = 'insert'
|
|
||||||
..annotations.add(refer('override'))
|
|
||||||
..requiredParameters.add(new Parameter((b) => b..name = 'executor'))
|
|
||||||
..body = new Block((b) {
|
|
||||||
var inTransaction = new Method((b) {
|
|
||||||
b
|
|
||||||
..modifier = MethodModifier.async
|
|
||||||
..body = new Block((b) {
|
|
||||||
b.addExpression(refer('super')
|
|
||||||
.property('insert')
|
|
||||||
.call([refer('executor')])
|
|
||||||
.awaited
|
|
||||||
.assignVar('result'));
|
|
||||||
|
|
||||||
// Just call getOne() again
|
|
||||||
if (ctx.effectiveFields.any((f) =>
|
|
||||||
isSpecialId(ctx, f) ||
|
|
||||||
(ctx.columns[f.name]?.indexType ==
|
|
||||||
IndexType.primaryKey))) {
|
|
||||||
b.addExpression(refer('where')
|
|
||||||
.property('id')
|
|
||||||
.property('equals')
|
|
||||||
.call([
|
|
||||||
(refer('int')
|
|
||||||
.property('tryParse')
|
|
||||||
.call([refer('result').property('id')]))
|
|
||||||
]));
|
|
||||||
|
|
||||||
b.addExpression(refer('result').assign(
|
|
||||||
refer('getOne').call([refer('executor')]).awaited));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove - Fetch the results of @hasMany
|
|
||||||
// ctx.relations.forEach((name, relation) {
|
|
||||||
// if (relation.type == RelationshipType.hasMany) {
|
|
||||||
// // Call fetchLinked();
|
|
||||||
// var fetchLinked = refer('fetchLinked')
|
|
||||||
// .call([refer('result'), refer('executor')]).awaited;
|
|
||||||
// b.addExpression(refer('result').assign(fetchLinked));
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
b.addExpression(refer('result').returned);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
b.addExpression(refer('executor')
|
|
||||||
.property('transaction')
|
|
||||||
.call([inTransaction.closure]).returned);
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a Future<T> fetchLinked(T model, QueryExecutor), if necessary.
|
|
||||||
if (false &&
|
|
||||||
ctx.relations.values.any((r) => r.type == RelationshipType.hasMany)) {
|
|
||||||
clazz.methods.add(new Method((b) {
|
|
||||||
b
|
|
||||||
..name = 'fetchLinked'
|
|
||||||
..modifier = MethodModifier.async
|
|
||||||
..returns = new TypeReference((b) {
|
|
||||||
b
|
|
||||||
..symbol = 'Future'
|
|
||||||
..types.add(ctx.buildContext.modelClassType);
|
|
||||||
})
|
|
||||||
..requiredParameters.addAll([
|
|
||||||
new Parameter((b) => b
|
|
||||||
..name = 'model'
|
|
||||||
..type = ctx.buildContext.modelClassType),
|
|
||||||
new Parameter((b) => b
|
|
||||||
..name = 'executor'
|
|
||||||
..type = refer('QueryExecutor')),
|
|
||||||
])
|
|
||||||
..body = new Block((b) {
|
|
||||||
var args = <String, Expression>{};
|
|
||||||
|
|
||||||
ctx.relations.forEach((name, relation) {
|
|
||||||
// TODO: Should this be entirely removed?
|
|
||||||
if (relation.type == RelationshipType.hasMany) {
|
|
||||||
// For each hasMany, we need to create a query of
|
|
||||||
// the corresponding type.
|
|
||||||
var foreign = relation.foreign;
|
|
||||||
var queryType = refer(
|
|
||||||
'${foreign.buildContext.modelClassNameRecase.pascalCase}Query');
|
|
||||||
var queryInstance = queryType.newInstance([]);
|
|
||||||
|
|
||||||
// Next, we need to apply a cascade that sets the correct query value.
|
|
||||||
var localField = relation.findLocalField(ctx);
|
|
||||||
var foreignField = relation.findForeignField(ctx);
|
|
||||||
|
|
||||||
var queryValue = (isSpecialId(ctx, localField))
|
|
||||||
? 'int.parse(model.id)'
|
|
||||||
: 'model.${localField.name}';
|
|
||||||
var cascadeText =
|
|
||||||
'..where.${foreignField.name}.equals($queryValue)';
|
|
||||||
var queryText = queryInstance.accept(new DartEmitter());
|
|
||||||
var combinedExpr =
|
|
||||||
new CodeExpression(new Code('($queryText$cascadeText)'));
|
|
||||||
|
|
||||||
// Finally, just call get and await it.
|
|
||||||
var expr = combinedExpr
|
|
||||||
.property('get')
|
|
||||||
.call([refer('executor')]).awaited;
|
|
||||||
args[name] = expr;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Just return a copyWith
|
|
||||||
b.addExpression(
|
|
||||||
refer('model').property('copyWith').call([], args).returned);
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also, if there is a @HasMany, generate overrides for query methods that
|
// Also, if there is a @HasMany, generate overrides for query methods that
|
||||||
// execute in a transaction, and invoke fetchLinked.
|
// execute in a transaction, and invoke fetchLinked.
|
||||||
if (ctx.relations.values.any((r) => r.type == RelationshipType.hasMany)) {
|
if (ctx.relations.values.any((r) => r.type == RelationshipType.hasMany)) {
|
||||||
for (var methodName in const ['get', 'update', 'delete']) {
|
for (var methodName in const ['get', 'update', 'delete']) {
|
||||||
clazz.methods.add(new Method((b) {
|
clazz.methods.add(Method((b) {
|
||||||
var type = ctx.buildContext.modelClassType.accept(DartEmitter());
|
var type = ctx.buildContext.modelClassType.accept(DartEmitter());
|
||||||
b
|
b
|
||||||
..name = methodName
|
..name = methodName
|
||||||
..annotations.add(refer('override'))
|
..annotations.add(refer('override'))
|
||||||
..requiredParameters.add(new Parameter((b) => b
|
..requiredParameters.add(Parameter((b) => b
|
||||||
..name = 'executor'
|
..name = 'executor'
|
||||||
..type = refer('QueryExecutor')));
|
..type = refer('QueryExecutor')));
|
||||||
|
|
||||||
|
@ -500,7 +465,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
'@HasMany and @ManyToMany relations require a primary key to be defined on the model.';
|
'@HasMany and @ManyToMany relations require a primary key to be defined on the model.';
|
||||||
}
|
}
|
||||||
|
|
||||||
b.body = new Code('''
|
b.body = Code('''
|
||||||
return super.$methodName(executor).then((result) {
|
return super.$methodName(executor).then((result) {
|
||||||
return result.fold<List<$type>>([], (out, model) {
|
return result.fold<List<$type>>([], (out, model) {
|
||||||
var idx = out.indexWhere((m) => m.$keyName == model.$keyName);
|
var idx = out.indexWhere((m) => m.$keyName == model.$keyName);
|
||||||
|
@ -521,19 +486,19 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Class buildWhereClass(OrmBuildContext ctx) {
|
Class buildWhereClass(OrmBuildContext ctx) {
|
||||||
return new Class((clazz) {
|
return Class((clazz) {
|
||||||
var rc = ctx.buildContext.modelClassNameRecase;
|
var rc = ctx.buildContext.modelClassNameRecase;
|
||||||
clazz
|
clazz
|
||||||
..name = '${rc.pascalCase}QueryWhere'
|
..name = '${rc.pascalCase}QueryWhere'
|
||||||
..extend = refer('QueryWhere');
|
..extend = refer('QueryWhere');
|
||||||
|
|
||||||
// Build expressionBuilders getter
|
// Build expressionBuilders getter
|
||||||
clazz.methods.add(new Method((m) {
|
clazz.methods.add(Method((m) {
|
||||||
m
|
m
|
||||||
..name = 'expressionBuilders'
|
..name = 'expressionBuilders'
|
||||||
..annotations.add(refer('override'))
|
..annotations.add(refer('override'))
|
||||||
..type = MethodType.getter
|
..type = MethodType.getter
|
||||||
..body = new Block((b) {
|
..body = Block((b) {
|
||||||
var references = ctx.effectiveFields.map((f) => refer(f.name));
|
var references = ctx.effectiveFields.map((f) => refer(f.name));
|
||||||
b.addExpression(literalList(references).returned);
|
b.addExpression(literalList(references).returned);
|
||||||
});
|
});
|
||||||
|
@ -557,11 +522,11 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
if (const TypeChecker.fromRuntime(int).isExactlyType(type) ||
|
if (const TypeChecker.fromRuntime(int).isExactlyType(type) ||
|
||||||
const TypeChecker.fromRuntime(double).isExactlyType(type) ||
|
const TypeChecker.fromRuntime(double).isExactlyType(type) ||
|
||||||
isSpecialId(ctx, field)) {
|
isSpecialId(ctx, field)) {
|
||||||
builderType = new TypeReference((b) => b
|
builderType = TypeReference((b) => b
|
||||||
..symbol = 'NumericSqlExpressionBuilder'
|
..symbol = 'NumericSqlExpressionBuilder'
|
||||||
..types.add(refer(isSpecialId(ctx, field) ? 'int' : type.name)));
|
..types.add(refer(isSpecialId(ctx, field) ? 'int' : type.name)));
|
||||||
} else if (type is InterfaceType && type.element.isEnum) {
|
} else if (type is InterfaceType && type.element.isEnum) {
|
||||||
builderType = new TypeReference((b) => b
|
builderType = TypeReference((b) => b
|
||||||
..symbol = 'EnumSqlExpressionBuilder'
|
..symbol = 'EnumSqlExpressionBuilder'
|
||||||
..types.add(convertTypeReference(type)));
|
..types.add(convertTypeReference(type)));
|
||||||
args.add(CodeExpression(Code('(v) => v.index')));
|
args.add(CodeExpression(Code('(v) => v.index')));
|
||||||
|
@ -580,20 +545,20 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
builderType = refer('ListSqlExpressionBuilder');
|
builderType = refer('ListSqlExpressionBuilder');
|
||||||
} else if (ctx.relations.containsKey(field.name)) {
|
} else if (ctx.relations.containsKey(field.name)) {
|
||||||
var relation = ctx.relations[field.name];
|
var relation = ctx.relations[field.name];
|
||||||
if (relation.type != RelationshipType.belongsTo)
|
if (relation.type != RelationshipType.belongsTo) {
|
||||||
continue;
|
continue;
|
||||||
else {
|
} else {
|
||||||
builderType = new TypeReference((b) => b
|
builderType = TypeReference((b) => b
|
||||||
..symbol = 'NumericSqlExpressionBuilder'
|
..symbol = 'NumericSqlExpressionBuilder'
|
||||||
..types.add(refer('int')));
|
..types.add(refer('int')));
|
||||||
name = relation.localKey;
|
name = relation.localKey;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new UnsupportedError(
|
throw UnsupportedError(
|
||||||
'Cannot generate ORM code for field of type ${field.type.name}.');
|
'Cannot generate ORM code for field of type ${field.type.name}.');
|
||||||
}
|
}
|
||||||
|
|
||||||
clazz.fields.add(new Field((b) {
|
clazz.fields.add(Field((b) {
|
||||||
b
|
b
|
||||||
..name = name
|
..name = name
|
||||||
..modifier = FieldModifier.final$
|
..modifier = FieldModifier.final$
|
||||||
|
@ -611,9 +576,9 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, just add a constructor that initializes each builder.
|
// Now, just add a constructor that initializes each builder.
|
||||||
clazz.constructors.add(new Constructor((b) {
|
clazz.constructors.add(Constructor((b) {
|
||||||
b
|
b
|
||||||
..requiredParameters.add(new Parameter((b) => b
|
..requiredParameters.add(Parameter((b) => b
|
||||||
..name = 'query'
|
..name = 'query'
|
||||||
..type = refer('${rc.pascalCase}Query')))
|
..type = refer('${rc.pascalCase}Query')))
|
||||||
..initializers.addAll(initializers);
|
..initializers.addAll(initializers);
|
||||||
|
@ -622,7 +587,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Class buildValuesClass(OrmBuildContext ctx) {
|
Class buildValuesClass(OrmBuildContext ctx) {
|
||||||
return new Class((clazz) {
|
return Class((clazz) {
|
||||||
var rc = ctx.buildContext.modelClassNameRecase;
|
var rc = ctx.buildContext.modelClassNameRecase;
|
||||||
clazz
|
clazz
|
||||||
..name = '${rc.pascalCase}QueryValues'
|
..name = '${rc.pascalCase}QueryValues'
|
||||||
|
@ -660,7 +625,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
var name = ctx.buildContext.resolveFieldName(field.name);
|
var name = ctx.buildContext.resolveFieldName(field.name);
|
||||||
var type = convertTypeReference(field.type);
|
var type = convertTypeReference(field.type);
|
||||||
|
|
||||||
clazz.methods.add(new Method((b) {
|
clazz.methods.add(Method((b) {
|
||||||
var value = refer('values').index(literalString(name));
|
var value = refer('values').index(literalString(name));
|
||||||
|
|
||||||
if (fType is InterfaceType && fType.element.isEnum) {
|
if (fType is InterfaceType && fType.element.isEnum) {
|
||||||
|
@ -684,10 +649,10 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
..name = field.name
|
..name = field.name
|
||||||
..type = MethodType.getter
|
..type = MethodType.getter
|
||||||
..returns = type
|
..returns = type
|
||||||
..body = new Block((b) => b.addExpression(value.returned));
|
..body = Block((b) => b.addExpression(value.returned));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
clazz.methods.add(new Method((b) {
|
clazz.methods.add(Method((b) {
|
||||||
Expression value = refer('value');
|
Expression value = refer('value');
|
||||||
|
|
||||||
if (fType is InterfaceType && fType.element.isEnum) {
|
if (fType is InterfaceType && fType.element.isEnum) {
|
||||||
|
@ -702,7 +667,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
b
|
b
|
||||||
..name = field.name
|
..name = field.name
|
||||||
..type = MethodType.setter
|
..type = MethodType.setter
|
||||||
..requiredParameters.add(new Parameter((b) => b
|
..requiredParameters.add(Parameter((b) => b
|
||||||
..name = 'value'
|
..name = 'value'
|
||||||
..type = type))
|
..type = type))
|
||||||
..body =
|
..body =
|
||||||
|
@ -711,17 +676,18 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an copyFrom(model)
|
// Add an copyFrom(model)
|
||||||
clazz.methods.add(new Method((b) {
|
clazz.methods.add(Method((b) {
|
||||||
b
|
b
|
||||||
..name = 'copyFrom'
|
..name = 'copyFrom'
|
||||||
..returns = refer('void')
|
..returns = refer('void')
|
||||||
..requiredParameters.add(new Parameter((b) => b
|
..requiredParameters.add(Parameter((b) => b
|
||||||
..name = 'model'
|
..name = 'model'
|
||||||
..type = ctx.buildContext.modelClassType))
|
..type = ctx.buildContext.modelClassType))
|
||||||
..body = new Block((b) {
|
..body = Block((b) {
|
||||||
for (var field in ctx.effectiveFields) {
|
for (var field in ctx.effectiveFields) {
|
||||||
if (isSpecialId(ctx, field) || field is RelationFieldImpl)
|
if (isSpecialId(ctx, field) || field is RelationFieldImpl) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
b.addExpression(refer(field.name)
|
b.addExpression(refer(field.name)
|
||||||
.assign(refer('model').property(field.name)));
|
.assign(refer('model').property(field.name)));
|
||||||
}
|
}
|
||||||
|
@ -744,11 +710,11 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
}
|
}
|
||||||
|
|
||||||
var cond = prop.notEqualTo(literalNull);
|
var cond = prop.notEqualTo(literalNull);
|
||||||
var condStr = cond.accept(new DartEmitter());
|
var condStr = cond.accept(DartEmitter());
|
||||||
var blkStr =
|
var blkStr =
|
||||||
new Block((b) => b.addExpression(target.assign(parsedId)))
|
Block((b) => b.addExpression(target.assign(parsedId)))
|
||||||
.accept(new DartEmitter());
|
.accept(DartEmitter());
|
||||||
var ifStmt = new Code('if ($condStr) { $blkStr }');
|
var ifStmt = Code('if ($condStr) { $blkStr }');
|
||||||
b.statements.add(ifStmt);
|
b.statements.add(ifStmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:angel_orm/angel_orm.dart';
|
||||||
import 'package:source_gen/source_gen.dart';
|
import 'package:source_gen/source_gen.dart';
|
||||||
import 'orm_build_context.dart';
|
import 'orm_build_context.dart';
|
||||||
|
|
||||||
const TypeChecker columnTypeChecker = const TypeChecker.fromRuntime(Column);
|
const TypeChecker columnTypeChecker = TypeChecker.fromRuntime(Column);
|
||||||
|
|
||||||
Orm reviveORMAnnotation(ConstantReader reader) {
|
Orm reviveORMAnnotation(ConstantReader reader) {
|
||||||
return Orm(
|
return Orm(
|
||||||
|
@ -34,6 +34,7 @@ class RelationshipReader {
|
||||||
final DartType through;
|
final DartType through;
|
||||||
final OrmBuildContext foreign;
|
final OrmBuildContext foreign;
|
||||||
final OrmBuildContext throughContext;
|
final OrmBuildContext throughContext;
|
||||||
|
final JoinType joinType;
|
||||||
|
|
||||||
const RelationshipReader(this.type,
|
const RelationshipReader(this.type,
|
||||||
{this.localKey,
|
{this.localKey,
|
||||||
|
@ -42,11 +43,29 @@ class RelationshipReader {
|
||||||
this.cascadeOnDelete,
|
this.cascadeOnDelete,
|
||||||
this.through,
|
this.through,
|
||||||
this.foreign,
|
this.foreign,
|
||||||
this.throughContext});
|
this.throughContext,
|
||||||
|
this.joinType});
|
||||||
|
|
||||||
bool get isManyToMany =>
|
bool get isManyToMany =>
|
||||||
type == RelationshipType.hasMany && throughContext != null;
|
type == RelationshipType.hasMany && throughContext != null;
|
||||||
|
|
||||||
|
String get joinTypeString {
|
||||||
|
switch (joinType ?? JoinType.left) {
|
||||||
|
case JoinType.inner:
|
||||||
|
return 'join';
|
||||||
|
case JoinType.left:
|
||||||
|
return 'leftJoin';
|
||||||
|
case JoinType.right:
|
||||||
|
return 'rightJoin';
|
||||||
|
case JoinType.full:
|
||||||
|
return 'fullOuterJoin';
|
||||||
|
case JoinType.self:
|
||||||
|
return 'selfJoin';
|
||||||
|
default:
|
||||||
|
return 'join';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FieldElement findLocalField(OrmBuildContext ctx) {
|
FieldElement findLocalField(OrmBuildContext ctx) {
|
||||||
return ctx.effectiveFields.firstWhere(
|
return ctx.effectiveFields.firstWhere(
|
||||||
(f) => ctx.buildContext.resolveFieldName(f.name) == localKey,
|
(f) => ctx.buildContext.resolveFieldName(f.name) == localKey,
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
name: angel_orm_generator
|
name: angel_orm_generator
|
||||||
version: 2.0.5
|
version: 2.1.0-beta.1
|
||||||
description: Code generators for Angel's ORM. Generates query builder classes.
|
description: Code generators for Angel's ORM. Generates query builder classes.
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
homepage: https://github.com/angel-dart/orm
|
homepage: https://github.com/angel-dart/orm
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.0.0-dev <3.0.0"
|
sdk: ">=2.0.0<3.0.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
analyzer: ">=0.27.1 <2.0.0"
|
analyzer: ">=0.35.0 <2.0.0"
|
||||||
angel_model: ^1.0.0
|
angel_model: ^1.0.0
|
||||||
angel_serialize: ^2.0.0
|
angel_serialize: ^2.0.0
|
||||||
angel_orm: ^2.0.0-dev
|
angel_orm: ^2.1.0-beta
|
||||||
angel_serialize_generator: ^2.0.0
|
angel_serialize_generator: ^2.0.0
|
||||||
build: ">=0.12.0 <2.0.0"
|
build: ^1.0.0
|
||||||
build_config: ">=0.3.0 <0.5.0"
|
build_config: ^0.4.0
|
||||||
code_builder: ^3.0.0
|
code_builder: ^3.0.0
|
||||||
dart_style: ^1.0.0
|
dart_style: ^1.0.0
|
||||||
inflection2: ^0.4.2
|
inflection2: ^0.4.2
|
||||||
|
@ -23,12 +23,11 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angel_framework: ^2.0.0-alpha
|
angel_framework: ^2.0.0-alpha
|
||||||
angel_migration:
|
angel_migration:
|
||||||
git:
|
path: ../angel_migration
|
||||||
url: https://github.com/angel-dart/migration
|
|
||||||
path: angel_migration
|
|
||||||
#angel_test: ^1.0.0
|
#angel_test: ^1.0.0
|
||||||
build_runner: ^1.0.0
|
build_runner: ^1.0.0
|
||||||
collection: ^1.0.0
|
collection: ^1.0.0
|
||||||
|
pedantic: ^1.0.0
|
||||||
postgres: ^1.0.0
|
postgres: ^1.0.0
|
||||||
test: ^1.0.0
|
test: ^1.0.0
|
||||||
# dependency_overrides:
|
# dependency_overrides:
|
||||||
|
|
|
@ -153,7 +153,7 @@ class TodoQueryValues extends MapQueryValues {
|
||||||
class Todo extends _Todo {
|
class Todo extends _Todo {
|
||||||
Todo(
|
Todo(
|
||||||
{this.id,
|
{this.id,
|
||||||
@required this.isComplete = false,
|
this.isComplete = false,
|
||||||
this.text,
|
this.text,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
this.updatedAt});
|
this.updatedAt});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
||||||
import 'package:angel_orm/angel_orm.dart';
|
import 'package:angel_orm/angel_orm.dart';
|
||||||
import 'package:angel_orm/src/query.dart';
|
import 'package:angel_orm/src/query.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:pool/pool.dart';
|
// import 'package:pool/pool.dart';
|
||||||
import 'package:sqljocky5/connection/connection.dart';
|
import 'package:sqljocky5/connection/connection.dart';
|
||||||
import 'package:sqljocky5/sqljocky.dart';
|
import 'package:sqljocky5/sqljocky.dart';
|
||||||
|
|
||||||
|
|
|
@ -17,4 +17,7 @@ dev_dependencies:
|
||||||
angel_orm_test:
|
angel_orm_test:
|
||||||
path: ../angel_orm_test
|
path: ../angel_orm_test
|
||||||
build_runner: ^1.0.0
|
build_runner: ^1.0.0
|
||||||
test: ^1.0.0
|
test: ^1.0.0
|
||||||
|
dependency_overrides:
|
||||||
|
angel_migration:
|
||||||
|
path: ../angel_migration
|
|
@ -1,3 +1,6 @@
|
||||||
|
# 1.1.0-beta
|
||||||
|
* Updates for `package:angel_orm@2.1.0-beta`.
|
||||||
|
|
||||||
# 1.0.0
|
# 1.0.0
|
||||||
* Bump to `1.0.0`. This package has actually been stable for several months.
|
* Bump to `1.0.0`. This package has actually been stable for several months.
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,13 @@ class PostgreSqlExecutor extends QueryExecutor {
|
||||||
PostgreSQLExecutionContext get connection => _connection;
|
PostgreSQLExecutionContext get connection => _connection;
|
||||||
|
|
||||||
/// Closes the connection.
|
/// Closes the connection.
|
||||||
Future close() => (_connection as PostgreSQLConnection).close();
|
Future close() {
|
||||||
|
if (_connection is PostgreSQLConnection) {
|
||||||
|
return (_connection as PostgreSQLConnection).close();
|
||||||
|
} else {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<List>> query(
|
Future<List<List>> query(
|
||||||
|
@ -42,18 +48,16 @@ class PostgreSqlExecutor extends QueryExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<T> transaction<T>(FutureOr<T> Function() f) async {
|
Future<T> transaction<T>(FutureOr<T> Function(QueryExecutor) f) async {
|
||||||
if (_connection is! PostgreSQLConnection) return await f();
|
if (_connection is! PostgreSQLConnection) return await f(this);
|
||||||
var old = _connection;
|
|
||||||
T result;
|
T result;
|
||||||
try {
|
try {
|
||||||
logger?.fine('Entering transaction');
|
logger?.fine('Entering transaction');
|
||||||
await (_connection as PostgreSQLConnection).transaction((ctx) async {
|
await (_connection as PostgreSQLConnection).transaction((ctx) async {
|
||||||
_connection = ctx;
|
var tx = PostgreSqlExecutor(ctx, logger: logger);
|
||||||
result = await f();
|
result = await f(tx);
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
_connection = old;
|
|
||||||
logger?.fine('Exiting transaction');
|
logger?.fine('Exiting transaction');
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -130,7 +134,7 @@ class PostgreSqlExecutorPool extends QueryExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<T> transaction<T>(FutureOr<T> Function() f) {
|
Future<T> transaction<T>(FutureOr<T> Function(QueryExecutor) f) {
|
||||||
return _pool.withResource(() async {
|
return _pool.withResource(() async {
|
||||||
var executor = await _next();
|
var executor = await _next();
|
||||||
return executor.transaction(f);
|
return executor.transaction(f);
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
name: angel_orm_postgres
|
name: angel_orm_postgres
|
||||||
version: 1.0.0
|
version: 1.1.0-beta
|
||||||
description: PostgreSQL support for Angel's ORM. Includes functionality for querying and transactions.
|
description: PostgreSQL support for Angel's ORM. Includes functionality for querying and transactions.
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
homepage: https://github.com/angel-dart/orm
|
homepage: https://github.com/angel-dart/orm
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.0.0-dev.1.2 <3.0.0'
|
sdk: '>=2.0.0-dev.1.2 <3.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
angel_orm: ^2.0.0-dev
|
angel_orm: ^2.1.0-beta
|
||||||
logging: ^0.11.0
|
logging: ^0.11.0
|
||||||
pool: ^1.0.0
|
pool: ^1.0.0
|
||||||
postgres: ^1.0.0
|
postgres: ^1.0.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angel_orm_test:
|
angel_orm_test:
|
||||||
path: ../angel_orm_test
|
path: ../angel_orm_test
|
||||||
test: ^1.0.0
|
pretty_logging: ^1.0.0
|
||||||
|
test: ^1.0.0
|
||||||
|
# dependency_overrides:
|
||||||
|
# angel_orm:
|
||||||
|
# path: ../angel_orm
|
|
@ -1,14 +1,13 @@
|
||||||
import 'package:angel_orm_test/angel_orm_test.dart';
|
import 'package:angel_orm_test/angel_orm_test.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:pretty_logging/pretty_logging.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
Logger.root.onRecord.listen((rec) {
|
Logger.root
|
||||||
print(rec);
|
..level = Level.ALL
|
||||||
if (rec.error != null) print(rec.error);
|
..onRecord.listen(prettyLog);
|
||||||
if (rec.stackTrace != null) print(rec.stackTrace);
|
|
||||||
});
|
|
||||||
|
|
||||||
group('postgresql', () {
|
group('postgresql', () {
|
||||||
group('belongsTo',
|
group('belongsTo',
|
||||||
|
|
|
@ -179,7 +179,7 @@ class Todo extends _Todo {
|
||||||
String text,
|
String text,
|
||||||
DateTime createdAt,
|
DateTime createdAt,
|
||||||
DateTime updatedAt}) {
|
DateTime updatedAt}) {
|
||||||
return new Todo(
|
return Todo(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
isComplete: isComplete ?? this.isComplete,
|
isComplete: isComplete ?? this.isComplete,
|
||||||
text: text ?? this.text,
|
text: text ?? this.text,
|
||||||
|
@ -215,7 +215,7 @@ class Todo extends _Todo {
|
||||||
// SerializerGenerator
|
// SerializerGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
const TodoSerializer todoSerializer = const TodoSerializer();
|
const TodoSerializer todoSerializer = TodoSerializer();
|
||||||
|
|
||||||
class TodoEncoder extends Converter<Todo, Map> {
|
class TodoEncoder extends Converter<Todo, Map> {
|
||||||
const TodoEncoder();
|
const TodoEncoder();
|
||||||
|
@ -240,10 +240,10 @@ class TodoSerializer extends Codec<Todo, Map> {
|
||||||
get decoder => const TodoDecoder();
|
get decoder => const TodoDecoder();
|
||||||
static Todo fromMap(Map map) {
|
static Todo fromMap(Map map) {
|
||||||
if (map['text'] == null) {
|
if (map['text'] == null) {
|
||||||
throw new FormatException("Missing required field 'text' on Todo.");
|
throw FormatException("Missing required field 'text' on Todo.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Todo(
|
return Todo(
|
||||||
id: map['id'] as String,
|
id: map['id'] as String,
|
||||||
isComplete: map['is_complete'] as bool ?? false,
|
isComplete: map['is_complete'] as bool ?? false,
|
||||||
text: map['text'] as String,
|
text: map['text'] as String,
|
||||||
|
@ -264,7 +264,7 @@ class TodoSerializer extends Codec<Todo, Map> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (model.text == null) {
|
if (model.text == null) {
|
||||||
throw new FormatException("Missing required field 'text' on Todo.");
|
throw FormatException("Missing required field 'text' on Todo.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -67,9 +67,9 @@ class OrmService<Id, Data, TQuery extends Query<Data, QueryWhere>>
|
||||||
if (v is Map) {
|
if (v is Map) {
|
||||||
v.forEach((key, value) {
|
v.forEach((key, value) {
|
||||||
var descending = false;
|
var descending = false;
|
||||||
if (value is String)
|
if (value is String) {
|
||||||
descending = value == '-1';
|
descending = value == '-1';
|
||||||
else if (value is num) descending = value.toInt() == -1;
|
} else if (value is num) descending = value.toInt() == -1;
|
||||||
query.orderBy(key.toString(), descending: descending);
|
query.orderBy(key.toString(), descending: descending);
|
||||||
});
|
});
|
||||||
} else if (v is String) {
|
} else if (v is String) {
|
||||||
|
@ -120,8 +120,7 @@ class OrmService<Id, Data, TQuery extends Query<Data, QueryWhere>>
|
||||||
await _applyQuery(query, params);
|
await _applyQuery(query, params);
|
||||||
var result = await query.getOne(executor);
|
var result = await query.getOne(executor);
|
||||||
if (result != null) return result;
|
if (result != null) return result;
|
||||||
throw new AngelHttpException.notFound(
|
throw AngelHttpException.notFound(message: 'No record found for ID $id');
|
||||||
message: 'No record found for ID $id');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -133,7 +132,7 @@ class OrmService<Id, Data, TQuery extends Query<Data, QueryWhere>>
|
||||||
await _applyQuery(query, params);
|
await _applyQuery(query, params);
|
||||||
var result = await query.getOne(executor);
|
var result = await query.getOne(executor);
|
||||||
if (result != null) return result;
|
if (result != null) return result;
|
||||||
throw new AngelHttpException.notFound(message: errorMessage);
|
throw AngelHttpException.notFound(message: errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -170,8 +169,7 @@ class OrmService<Id, Data, TQuery extends Query<Data, QueryWhere>>
|
||||||
|
|
||||||
var result = await query.updateOne(executor);
|
var result = await query.updateOne(executor);
|
||||||
if (result != null) return result;
|
if (result != null) return result;
|
||||||
throw new AngelHttpException.notFound(
|
throw AngelHttpException.notFound(message: 'No record found for ID $id');
|
||||||
message: 'No record found for ID $id');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -192,7 +190,6 @@ class OrmService<Id, Data, TQuery extends Query<Data, QueryWhere>>
|
||||||
|
|
||||||
var result = await query.deleteOne(executor);
|
var result = await query.deleteOne(executor);
|
||||||
if (result != null) return result;
|
if (result != null) return result;
|
||||||
throw new AngelHttpException.notFound(
|
throw AngelHttpException.notFound(message: 'No record found for ID $id');
|
||||||
message: 'No record found for ID $id');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,4 +23,7 @@ dev_dependencies:
|
||||||
logging: ^0.11.0
|
logging: ^0.11.0
|
||||||
pedantic: ^1.0.0
|
pedantic: ^1.0.0
|
||||||
postgres: ^1.0.0
|
postgres: ^1.0.0
|
||||||
test: ^1.0.0
|
test: ^1.0.0
|
||||||
|
dependency_overrides:
|
||||||
|
angel_migration:
|
||||||
|
path: ../angel_migration
|
|
@ -238,7 +238,7 @@ class Pokemon extends _Pokemon {
|
||||||
PokemonType type2,
|
PokemonType type2,
|
||||||
DateTime createdAt,
|
DateTime createdAt,
|
||||||
DateTime updatedAt}) {
|
DateTime updatedAt}) {
|
||||||
return new Pokemon(
|
return Pokemon(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
species: species ?? this.species,
|
species: species ?? this.species,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
|
@ -281,7 +281,7 @@ class Pokemon extends _Pokemon {
|
||||||
// SerializerGenerator
|
// SerializerGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
const PokemonSerializer pokemonSerializer = const PokemonSerializer();
|
const PokemonSerializer pokemonSerializer = PokemonSerializer();
|
||||||
|
|
||||||
class PokemonEncoder extends Converter<Pokemon, Map> {
|
class PokemonEncoder extends Converter<Pokemon, Map> {
|
||||||
const PokemonEncoder();
|
const PokemonEncoder();
|
||||||
|
@ -306,18 +306,18 @@ class PokemonSerializer extends Codec<Pokemon, Map> {
|
||||||
get decoder => const PokemonDecoder();
|
get decoder => const PokemonDecoder();
|
||||||
static Pokemon fromMap(Map map) {
|
static Pokemon fromMap(Map map) {
|
||||||
if (map['species'] == null) {
|
if (map['species'] == null) {
|
||||||
throw new FormatException("Missing required field 'species' on Pokemon.");
|
throw FormatException("Missing required field 'species' on Pokemon.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map['level'] == null) {
|
if (map['level'] == null) {
|
||||||
throw new FormatException("Missing required field 'level' on Pokemon.");
|
throw FormatException("Missing required field 'level' on Pokemon.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map['type1'] == null) {
|
if (map['type1'] == null) {
|
||||||
throw new FormatException("Missing required field 'type1' on Pokemon.");
|
throw FormatException("Missing required field 'type1' on Pokemon.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Pokemon(
|
return Pokemon(
|
||||||
id: map['id'] as String,
|
id: map['id'] as String,
|
||||||
species: map['species'] as String,
|
species: map['species'] as String,
|
||||||
name: map['name'] as String,
|
name: map['name'] as String,
|
||||||
|
@ -349,15 +349,15 @@ class PokemonSerializer extends Codec<Pokemon, Map> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (model.species == null) {
|
if (model.species == null) {
|
||||||
throw new FormatException("Missing required field 'species' on Pokemon.");
|
throw FormatException("Missing required field 'species' on Pokemon.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.level == null) {
|
if (model.level == null) {
|
||||||
throw new FormatException("Missing required field 'level' on Pokemon.");
|
throw FormatException("Missing required field 'level' on Pokemon.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.type1 == null) {
|
if (model.type1 == null) {
|
||||||
throw new FormatException("Missing required field 'type1' on Pokemon.");
|
throw FormatException("Missing required field 'type1' on Pokemon.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'package:angel_orm/angel_orm.dart';
|
import 'package:angel_orm/angel_orm.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'models/book.dart';
|
import 'models/book.dart';
|
||||||
|
import 'util.dart';
|
||||||
|
|
||||||
belongsToTests(FutureOr<QueryExecutor> Function() createExecutor,
|
belongsToTests(FutureOr<QueryExecutor> Function() createExecutor,
|
||||||
{FutureOr<void> Function(QueryExecutor) close}) {
|
{FutureOr<void> Function(QueryExecutor) close}) {
|
||||||
|
@ -124,8 +125,9 @@ belongsToTests(FutureOr<QueryExecutor> Function() createExecutor,
|
||||||
});
|
});
|
||||||
|
|
||||||
test('delete stream', () async {
|
test('delete stream', () async {
|
||||||
|
printSeparator('Delete stream test');
|
||||||
var query = new BookQuery()..where.name.equals(deathlyHallows.name);
|
var query = new BookQuery()..where.name.equals(deathlyHallows.name);
|
||||||
print(query.compile(Set()));
|
print(query.compile(Set(), preamble: 'DELETE', withFields: false));
|
||||||
var books = await query.delete(executor);
|
var books = await query.delete(executor);
|
||||||
expect(books, hasLength(1));
|
expect(books, hasLength(1));
|
||||||
|
|
||||||
|
@ -146,4 +148,21 @@ belongsToTests(FutureOr<QueryExecutor> Function() createExecutor,
|
||||||
expect(book.author, isNotNull);
|
expect(book.author, isNotNull);
|
||||||
expect(book.author.name, jkRowling.name);
|
expect(book.author.name, jkRowling.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('joined subquery', () {
|
||||||
|
// To verify that the joined subquery is correct,
|
||||||
|
// we test both a query that return empty, and one
|
||||||
|
// that should return correctly.
|
||||||
|
test('returns empty on false subquery', () async {
|
||||||
|
printSeparator('False subquery test');
|
||||||
|
var query = BookQuery()..author.where.name.equals('Billie Jean');
|
||||||
|
expect(await query.get(executor), isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns values on true subquery', () async {
|
||||||
|
printSeparator('True subquery test');
|
||||||
|
var query = BookQuery()..author.where.name.like('%Rowling%');
|
||||||
|
expect(await query.get(executor), [deathlyHallows]);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,5 +68,13 @@ hasManyTests(FutureOr<QueryExecutor> Function() createExecutor,
|
||||||
var tree = await tq.deleteOne(executor);
|
var tree = await tq.deleteOne(executor);
|
||||||
verify(tree);
|
verify(tree);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('returns empty on false subquery', () async {
|
||||||
|
var tq = new TreeQuery()
|
||||||
|
..where.id.equals(treeId)
|
||||||
|
..fruits.where.commonName.equals('Kiwi');
|
||||||
|
var tree = await tq.getOne(executor);
|
||||||
|
expect(tree.fruits, isEmpty);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,4 +85,12 @@ hasOneTests(FutureOr<QueryExecutor> Function() createExecutor,
|
||||||
expect(leg.foot.id, foot.id);
|
expect(leg.foot.id, foot.id);
|
||||||
expect(leg.foot.nToes, foot.nToes);
|
expect(leg.foot.nToes, foot.nToes);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('sets null on false subquery', () async {
|
||||||
|
var legQuery = new LegQuery()
|
||||||
|
..where.id.equals(originalLeg.idAsInt)
|
||||||
|
..foot.where.legId.equals(originalLeg.idAsInt + 1024);
|
||||||
|
var leg = await legQuery.getOne(executor);
|
||||||
|
expect(leg.foot, isNull);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
||||||
import 'package:angel_orm/angel_orm.dart';
|
import 'package:angel_orm/angel_orm.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'models/user.dart';
|
import 'models/user.dart';
|
||||||
|
import 'util.dart';
|
||||||
|
|
||||||
manyToManyTests(FutureOr<QueryExecutor> Function() createExecutor,
|
manyToManyTests(FutureOr<QueryExecutor> Function() createExecutor,
|
||||||
{FutureOr<void> Function(QueryExecutor) close}) {
|
{FutureOr<void> Function(QueryExecutor) close}) {
|
||||||
|
@ -61,6 +62,7 @@ manyToManyTests(FutureOr<QueryExecutor> Function() createExecutor,
|
||||||
print('=== THOSAKWE: ${thosakwe?.toJson()}');
|
print('=== THOSAKWE: ${thosakwe?.toJson()}');
|
||||||
|
|
||||||
// Allow thosakwe to publish...
|
// Allow thosakwe to publish...
|
||||||
|
printSeparator('Allow thosakwe to publish');
|
||||||
var thosakwePubQuery = RoleUserQuery();
|
var thosakwePubQuery = RoleUserQuery();
|
||||||
thosakwePubQuery.values
|
thosakwePubQuery.values
|
||||||
..userId = int.parse(thosakwe.id)
|
..userId = int.parse(thosakwe.id)
|
||||||
|
@ -68,6 +70,7 @@ manyToManyTests(FutureOr<QueryExecutor> Function() createExecutor,
|
||||||
await thosakwePubQuery.insert(executor);
|
await thosakwePubQuery.insert(executor);
|
||||||
|
|
||||||
// Allow thosakwe to subscribe...
|
// Allow thosakwe to subscribe...
|
||||||
|
printSeparator('Allow thosakwe to subscribe');
|
||||||
var thosakweSubQuery = RoleUserQuery();
|
var thosakweSubQuery = RoleUserQuery();
|
||||||
thosakweSubQuery.values
|
thosakweSubQuery.values
|
||||||
..userId = int.parse(thosakwe.id)
|
..userId = int.parse(thosakwe.id)
|
||||||
|
@ -78,8 +81,8 @@ manyToManyTests(FutureOr<QueryExecutor> Function() createExecutor,
|
||||||
// await dumpQuery('select * from users;');
|
// await dumpQuery('select * from users;');
|
||||||
// await dumpQuery('select * from roles;');
|
// await dumpQuery('select * from roles;');
|
||||||
// await dumpQuery('select * from role_users;');
|
// await dumpQuery('select * from role_users;');
|
||||||
var query = RoleQuery()..where.id.equals(canPub.idAsInt);
|
// var query = RoleQuery()..where.id.equals(canPub.idAsInt);
|
||||||
await dumpQuery(query.compile(Set()));
|
// await dumpQuery(query.compile(Set()));
|
||||||
|
|
||||||
print('\n');
|
print('\n');
|
||||||
print('==================================================');
|
print('==================================================');
|
||||||
|
@ -95,6 +98,7 @@ manyToManyTests(FutureOr<QueryExecutor> Function() createExecutor,
|
||||||
}
|
}
|
||||||
|
|
||||||
test('fetch roles for user', () async {
|
test('fetch roles for user', () async {
|
||||||
|
printSeparator('Fetch roles for user test');
|
||||||
var user = await fetchThosakwe();
|
var user = await fetchThosakwe();
|
||||||
expect(user.roles, hasLength(2));
|
expect(user.roles, hasLength(2));
|
||||||
expect(user.roles, contains(canPub));
|
expect(user.roles, contains(canPub));
|
||||||
|
@ -108,4 +112,21 @@ manyToManyTests(FutureOr<QueryExecutor> Function() createExecutor,
|
||||||
expect(r.users.toList(), [thosakwe]);
|
expect(r.users.toList(), [thosakwe]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('only fetches linked', () async {
|
||||||
|
// Create a new user. The roles list should be empty,
|
||||||
|
// be there are no related rules.
|
||||||
|
var userQuery = UserQuery();
|
||||||
|
userQuery.values
|
||||||
|
..username = 'Prince'
|
||||||
|
..password = 'Rogers'
|
||||||
|
..email = 'Nelson';
|
||||||
|
var user = await userQuery.insert(executor);
|
||||||
|
expect(user.roles, isEmpty);
|
||||||
|
|
||||||
|
// Fetch again, just to be doubly sure.
|
||||||
|
var query = UserQuery()..where.id.equals(user.idAsInt);
|
||||||
|
var fetched = await query.getOne(executor);
|
||||||
|
expect(fetched.roles, isEmpty);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ part 'book.g.dart';
|
||||||
@serializable
|
@serializable
|
||||||
@orm
|
@orm
|
||||||
class _Book extends Model {
|
class _Book extends Model {
|
||||||
@belongsTo
|
@BelongsTo(joinType: JoinType.inner)
|
||||||
_Author author;
|
_Author author;
|
||||||
|
|
||||||
@BelongsTo(localKey: "partner_author_id")
|
@BelongsTo(localKey: "partner_author_id", joinType: JoinType.inner)
|
||||||
_Author partnerAuthor;
|
_Author partnerAuthor;
|
||||||
|
|
||||||
String name;
|
String name;
|
||||||
|
|
|
@ -51,14 +51,16 @@ class AuthorMigration extends Migration {
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
class BookQuery extends Query<Book, BookQueryWhere> {
|
class BookQuery extends Query<Book, BookQueryWhere> {
|
||||||
BookQuery({Set<String> trampoline}) {
|
BookQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = BookQueryWhere(this);
|
_where = BookQueryWhere(this);
|
||||||
leftJoin('authors', 'author_id', 'id',
|
join(_author = AuthorQuery(trampoline: trampoline, parent: this),
|
||||||
|
'author_id', 'id',
|
||||||
additionalFields: const ['id', 'created_at', 'updated_at', 'name'],
|
additionalFields: const ['id', 'created_at', 'updated_at', 'name'],
|
||||||
trampoline: trampoline);
|
trampoline: trampoline);
|
||||||
leftJoin('authors', 'partner_author_id', 'id',
|
join(_partnerAuthor = AuthorQuery(trampoline: trampoline, parent: this),
|
||||||
|
'partner_author_id', 'id',
|
||||||
additionalFields: const ['id', 'created_at', 'updated_at', 'name'],
|
additionalFields: const ['id', 'created_at', 'updated_at', 'name'],
|
||||||
trampoline: trampoline);
|
trampoline: trampoline);
|
||||||
}
|
}
|
||||||
|
@ -68,6 +70,10 @@ class BookQuery extends Query<Book, BookQueryWhere> {
|
||||||
|
|
||||||
BookQueryWhere _where;
|
BookQueryWhere _where;
|
||||||
|
|
||||||
|
AuthorQuery _author;
|
||||||
|
|
||||||
|
AuthorQuery _partnerAuthor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get casts {
|
get casts {
|
||||||
return {};
|
return {};
|
||||||
|
@ -122,6 +128,14 @@ class BookQuery extends Query<Book, BookQueryWhere> {
|
||||||
deserialize(List row) {
|
deserialize(List row) {
|
||||||
return parseRow(row);
|
return parseRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AuthorQuery get author {
|
||||||
|
return _author;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorQuery get partnerAuthor {
|
||||||
|
return _partnerAuthor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BookQueryWhere extends QueryWhere {
|
class BookQueryWhere extends QueryWhere {
|
||||||
|
@ -202,7 +216,7 @@ class BookQueryValues extends MapQueryValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthorQuery extends Query<Author, AuthorQueryWhere> {
|
class AuthorQuery extends Query<Author, AuthorQueryWhere> {
|
||||||
AuthorQuery({Set<String> trampoline}) {
|
AuthorQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = AuthorQueryWhere(this);
|
_where = AuthorQueryWhere(this);
|
||||||
|
|
|
@ -31,7 +31,7 @@ class CarMigration extends Migration {
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
class CarQuery extends Query<Car, CarQueryWhere> {
|
class CarQuery extends Query<Car, CarQueryWhere> {
|
||||||
CarQuery({Set<String> trampoline}) {
|
CarQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = CarQueryWhere(this);
|
_where = CarQueryWhere(this);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:angel_migration/angel_migration.dart';
|
import 'package:angel_migration/angel_migration.dart';
|
||||||
import 'package:angel_model/angel_model.dart';
|
|
||||||
import 'package:angel_orm/angel_orm.dart';
|
import 'package:angel_orm/angel_orm.dart';
|
||||||
import 'package:angel_serialize/angel_serialize.dart';
|
import 'package:angel_serialize/angel_serialize.dart';
|
||||||
part 'email_indexed.g.dart';
|
part 'email_indexed.g.dart';
|
||||||
|
|
|
@ -60,11 +60,14 @@ class UserMigration extends Migration {
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
class RoleQuery extends Query<Role, RoleQueryWhere> {
|
class RoleQuery extends Query<Role, RoleQueryWhere> {
|
||||||
RoleQuery({Set<String> trampoline}) {
|
RoleQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = RoleQueryWhere(this);
|
_where = RoleQueryWhere(this);
|
||||||
leftJoin(RoleUserQuery(trampoline: trampoline), 'role', 'role_role',
|
leftJoin(
|
||||||
|
'(SELECT role_users.role_role, users.email, users.name, users.password FROM users LEFT JOIN role_users ON role_users.user_email=users.email)',
|
||||||
|
'role',
|
||||||
|
'role_role',
|
||||||
additionalFields: const ['email', 'name', 'password'],
|
additionalFields: const ['email', 'name', 'password'],
|
||||||
trampoline: trampoline);
|
trampoline: trampoline);
|
||||||
}
|
}
|
||||||
|
@ -209,13 +212,16 @@ class RoleQueryValues extends MapQueryValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoleUserQuery extends Query<RoleUser, RoleUserQueryWhere> {
|
class RoleUserQuery extends Query<RoleUser, RoleUserQueryWhere> {
|
||||||
RoleUserQuery({Set<String> trampoline}) {
|
RoleUserQuery({Query parent, Set<String> trampoline})
|
||||||
|
: super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = RoleUserQueryWhere(this);
|
_where = RoleUserQueryWhere(this);
|
||||||
leftJoin('roles', 'role_role', 'role',
|
leftJoin(_role = RoleQuery(trampoline: trampoline, parent: this),
|
||||||
|
'role_role', 'role',
|
||||||
additionalFields: const ['role'], trampoline: trampoline);
|
additionalFields: const ['role'], trampoline: trampoline);
|
||||||
leftJoin('users', 'user_email', 'email',
|
leftJoin(_user = UserQuery(trampoline: trampoline, parent: this),
|
||||||
|
'user_email', 'email',
|
||||||
additionalFields: const ['email', 'name', 'password'],
|
additionalFields: const ['email', 'name', 'password'],
|
||||||
trampoline: trampoline);
|
trampoline: trampoline);
|
||||||
}
|
}
|
||||||
|
@ -225,6 +231,10 @@ class RoleUserQuery extends Query<RoleUser, RoleUserQueryWhere> {
|
||||||
|
|
||||||
RoleUserQueryWhere _where;
|
RoleUserQueryWhere _where;
|
||||||
|
|
||||||
|
RoleQuery _role;
|
||||||
|
|
||||||
|
UserQuery _user;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get casts {
|
get casts {
|
||||||
return {};
|
return {};
|
||||||
|
@ -268,6 +278,14 @@ class RoleUserQuery extends Query<RoleUser, RoleUserQueryWhere> {
|
||||||
deserialize(List row) {
|
deserialize(List row) {
|
||||||
return parseRow(row);
|
return parseRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RoleQuery get role {
|
||||||
|
return _role;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserQuery get user {
|
||||||
|
return _user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoleUserQueryWhere extends QueryWhere {
|
class RoleUserQueryWhere extends QueryWhere {
|
||||||
|
@ -312,12 +330,16 @@ class RoleUserQueryValues extends MapQueryValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserQuery extends Query<User, UserQueryWhere> {
|
class UserQuery extends Query<User, UserQueryWhere> {
|
||||||
UserQuery({Set<String> trampoline}) {
|
UserQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = UserQueryWhere(this);
|
_where = UserQueryWhere(this);
|
||||||
leftJoin(RoleUserQuery(trampoline: trampoline), 'email', 'user_email',
|
leftJoin(
|
||||||
additionalFields: const ['role'], trampoline: trampoline);
|
'(SELECT role_users.user_email, roles.role FROM roles LEFT JOIN role_users ON role_users.role_role=roles.role)',
|
||||||
|
'email',
|
||||||
|
'user_email',
|
||||||
|
additionalFields: const ['role'],
|
||||||
|
trampoline: trampoline);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -2,13 +2,12 @@ import 'package:angel_migration/angel_migration.dart';
|
||||||
import 'package:angel_model/angel_model.dart';
|
import 'package:angel_model/angel_model.dart';
|
||||||
import 'package:angel_orm/angel_orm.dart';
|
import 'package:angel_orm/angel_orm.dart';
|
||||||
import 'package:angel_serialize/angel_serialize.dart';
|
import 'package:angel_serialize/angel_serialize.dart';
|
||||||
import 'package:meta/meta.dart';
|
// import 'car.dart';
|
||||||
import 'car.dart';
|
|
||||||
part 'has_car.g.dart';
|
part 'has_car.g.dart';
|
||||||
|
|
||||||
Map _carToMap(Car car) => car.toJson();
|
// Map _carToMap(Car car) => car.toJson();
|
||||||
|
|
||||||
Car _carFromMap(map) => CarSerializer.fromMap(map as Map);
|
// Car _carFromMap(map) => CarSerializer.fromMap(map as Map);
|
||||||
|
|
||||||
enum CarType { sedan, suv, atv }
|
enum CarType { sedan, suv, atv }
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class HasCarMigration extends Migration {
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
class HasCarQuery extends Query<HasCar, HasCarQueryWhere> {
|
class HasCarQuery extends Query<HasCar, HasCarQueryWhere> {
|
||||||
HasCarQuery({Set<String> trampoline}) {
|
HasCarQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = HasCarQueryWhere(this);
|
_where = HasCarQueryWhere(this);
|
||||||
|
|
|
@ -5,8 +5,8 @@ import 'package:angel_serialize/angel_serialize.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
part 'has_map.g.dart';
|
part 'has_map.g.dart';
|
||||||
|
|
||||||
String _boolToCustom(bool v) => v ? 'yes' : 'no';
|
// String _boolToCustom(bool v) => v ? 'yes' : 'no';
|
||||||
bool _customToBool(v) => v == 'yes';
|
// bool _customToBool(v) => v == 'yes';
|
||||||
|
|
||||||
@orm
|
@orm
|
||||||
@serializable
|
@serializable
|
||||||
|
|
|
@ -26,7 +26,7 @@ class HasMapMigration extends Migration {
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
class HasMapQuery extends Query<HasMap, HasMapQueryWhere> {
|
class HasMapQuery extends Query<HasMap, HasMapQueryWhere> {
|
||||||
HasMapQuery({Set<String> trampoline}) {
|
HasMapQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = HasMapQueryWhere(this);
|
_where = HasMapQueryWhere(this);
|
||||||
|
|
|
@ -46,11 +46,12 @@ class FootMigration extends Migration {
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
class LegQuery extends Query<Leg, LegQueryWhere> {
|
class LegQuery extends Query<Leg, LegQueryWhere> {
|
||||||
LegQuery({Set<String> trampoline}) {
|
LegQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = LegQueryWhere(this);
|
_where = LegQueryWhere(this);
|
||||||
leftJoin('feet', 'id', 'leg_id',
|
leftJoin(
|
||||||
|
_foot = FootQuery(trampoline: trampoline, parent: this), 'id', 'leg_id',
|
||||||
additionalFields: const [
|
additionalFields: const [
|
||||||
'id',
|
'id',
|
||||||
'created_at',
|
'created_at',
|
||||||
|
@ -66,6 +67,8 @@ class LegQuery extends Query<Leg, LegQueryWhere> {
|
||||||
|
|
||||||
LegQueryWhere _where;
|
LegQueryWhere _where;
|
||||||
|
|
||||||
|
FootQuery _foot;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get casts {
|
get casts {
|
||||||
return {};
|
return {};
|
||||||
|
@ -109,6 +112,10 @@ class LegQuery extends Query<Leg, LegQueryWhere> {
|
||||||
deserialize(List row) {
|
deserialize(List row) {
|
||||||
return parseRow(row);
|
return parseRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FootQuery get foot {
|
||||||
|
return _foot;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LegQueryWhere extends QueryWhere {
|
class LegQueryWhere extends QueryWhere {
|
||||||
|
@ -166,7 +173,7 @@ class LegQueryValues extends MapQueryValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
class FootQuery extends Query<Foot, FootQueryWhere> {
|
class FootQuery extends Query<Foot, FootQueryWhere> {
|
||||||
FootQuery({Set<String> trampoline}) {
|
FootQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = FootQueryWhere(this);
|
_where = FootQueryWhere(this);
|
||||||
|
|
|
@ -49,11 +49,12 @@ class CustomerMigration extends Migration {
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
class OrderQuery extends Query<Order, OrderQueryWhere> {
|
class OrderQuery extends Query<Order, OrderQueryWhere> {
|
||||||
OrderQuery({Set<String> trampoline}) {
|
OrderQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = OrderQueryWhere(this);
|
_where = OrderQueryWhere(this);
|
||||||
leftJoin('customers', 'customer_id', 'id',
|
leftJoin(_customer = CustomerQuery(trampoline: trampoline, parent: this),
|
||||||
|
'customer_id', 'id',
|
||||||
additionalFields: const ['id', 'created_at', 'updated_at'],
|
additionalFields: const ['id', 'created_at', 'updated_at'],
|
||||||
trampoline: trampoline);
|
trampoline: trampoline);
|
||||||
}
|
}
|
||||||
|
@ -63,6 +64,8 @@ class OrderQuery extends Query<Order, OrderQueryWhere> {
|
||||||
|
|
||||||
OrderQueryWhere _where;
|
OrderQueryWhere _where;
|
||||||
|
|
||||||
|
CustomerQuery _customer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get casts {
|
get casts {
|
||||||
return {};
|
return {};
|
||||||
|
@ -116,6 +119,10 @@ class OrderQuery extends Query<Order, OrderQueryWhere> {
|
||||||
deserialize(List row) {
|
deserialize(List row) {
|
||||||
return parseRow(row);
|
return parseRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CustomerQuery get customer {
|
||||||
|
return _customer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OrderQueryWhere extends QueryWhere {
|
class OrderQueryWhere extends QueryWhere {
|
||||||
|
@ -210,7 +217,8 @@ class OrderQueryValues extends MapQueryValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CustomerQuery extends Query<Customer, CustomerQueryWhere> {
|
class CustomerQuery extends Query<Customer, CustomerQueryWhere> {
|
||||||
CustomerQuery({Set<String> trampoline}) {
|
CustomerQuery({Query parent, Set<String> trampoline})
|
||||||
|
: super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = CustomerQueryWhere(this);
|
_where = CustomerQueryWhere(this);
|
||||||
|
|
|
@ -46,11 +46,12 @@ class FruitMigration extends Migration {
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
class TreeQuery extends Query<Tree, TreeQueryWhere> {
|
class TreeQuery extends Query<Tree, TreeQueryWhere> {
|
||||||
TreeQuery({Set<String> trampoline}) {
|
TreeQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = TreeQueryWhere(this);
|
_where = TreeQueryWhere(this);
|
||||||
leftJoin(FruitQuery(trampoline: trampoline), 'id', 'tree_id',
|
leftJoin(_fruits = FruitQuery(trampoline: trampoline, parent: this), 'id',
|
||||||
|
'tree_id',
|
||||||
additionalFields: const [
|
additionalFields: const [
|
||||||
'id',
|
'id',
|
||||||
'created_at',
|
'created_at',
|
||||||
|
@ -66,6 +67,8 @@ class TreeQuery extends Query<Tree, TreeQueryWhere> {
|
||||||
|
|
||||||
TreeQueryWhere _where;
|
TreeQueryWhere _where;
|
||||||
|
|
||||||
|
FruitQuery _fruits;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get casts {
|
get casts {
|
||||||
return {};
|
return {};
|
||||||
|
@ -112,6 +115,10 @@ class TreeQuery extends Query<Tree, TreeQueryWhere> {
|
||||||
return parseRow(row);
|
return parseRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FruitQuery get fruits {
|
||||||
|
return _fruits;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get(QueryExecutor executor) {
|
get(QueryExecutor executor) {
|
||||||
return super.get(executor).then((result) {
|
return super.get(executor).then((result) {
|
||||||
|
@ -225,7 +232,7 @@ class TreeQueryValues extends MapQueryValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
class FruitQuery extends Query<Fruit, FruitQueryWhere> {
|
class FruitQuery extends Query<Fruit, FruitQueryWhere> {
|
||||||
FruitQuery({Set<String> trampoline}) {
|
FruitQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = FruitQueryWhere(this);
|
_where = FruitQueryWhere(this);
|
||||||
|
|
|
@ -106,7 +106,8 @@ class FooPivotMigration extends Migration {
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
class UnorthodoxQuery extends Query<Unorthodox, UnorthodoxQueryWhere> {
|
class UnorthodoxQuery extends Query<Unorthodox, UnorthodoxQueryWhere> {
|
||||||
UnorthodoxQuery({Set<String> trampoline}) {
|
UnorthodoxQuery({Query parent, Set<String> trampoline})
|
||||||
|
: super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = UnorthodoxQueryWhere(this);
|
_where = UnorthodoxQueryWhere(this);
|
||||||
|
@ -183,13 +184,19 @@ class UnorthodoxQueryValues extends MapQueryValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
class WeirdJoinQuery extends Query<WeirdJoin, WeirdJoinQueryWhere> {
|
class WeirdJoinQuery extends Query<WeirdJoin, WeirdJoinQueryWhere> {
|
||||||
WeirdJoinQuery({Set<String> trampoline}) {
|
WeirdJoinQuery({Query parent, Set<String> trampoline})
|
||||||
|
: super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = WeirdJoinQueryWhere(this);
|
_where = WeirdJoinQueryWhere(this);
|
||||||
leftJoin('unorthodoxes', 'join_name', 'name',
|
leftJoin(
|
||||||
additionalFields: const ['name'], trampoline: trampoline);
|
_unorthodox = UnorthodoxQuery(trampoline: trampoline, parent: this),
|
||||||
leftJoin('songs', 'id', 'weird_join_id',
|
'join_name',
|
||||||
|
'name',
|
||||||
|
additionalFields: const ['name'],
|
||||||
|
trampoline: trampoline);
|
||||||
|
leftJoin(_song = SongQuery(trampoline: trampoline, parent: this), 'id',
|
||||||
|
'weird_join_id',
|
||||||
additionalFields: const [
|
additionalFields: const [
|
||||||
'id',
|
'id',
|
||||||
'created_at',
|
'created_at',
|
||||||
|
@ -198,10 +205,15 @@ class WeirdJoinQuery extends Query<WeirdJoin, WeirdJoinQueryWhere> {
|
||||||
'title'
|
'title'
|
||||||
],
|
],
|
||||||
trampoline: trampoline);
|
trampoline: trampoline);
|
||||||
leftJoin(NumbaQuery(trampoline: trampoline), 'id', 'parent',
|
leftJoin(_numbas = NumbaQuery(trampoline: trampoline, parent: this), 'id',
|
||||||
|
'parent',
|
||||||
additionalFields: const ['i', 'parent'], trampoline: trampoline);
|
additionalFields: const ['i', 'parent'], trampoline: trampoline);
|
||||||
leftJoin(FooPivotQuery(trampoline: trampoline), 'id', 'weird_join_id',
|
leftJoin(
|
||||||
additionalFields: const ['bar'], trampoline: trampoline);
|
'(SELECT foo_pivots.weird_join_id, foos.bar FROM foos LEFT JOIN foo_pivots ON foo_pivots.foo_bar=foos.bar)',
|
||||||
|
'id',
|
||||||
|
'weird_join_id',
|
||||||
|
additionalFields: const ['bar'],
|
||||||
|
trampoline: trampoline);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -209,6 +221,12 @@ class WeirdJoinQuery extends Query<WeirdJoin, WeirdJoinQueryWhere> {
|
||||||
|
|
||||||
WeirdJoinQueryWhere _where;
|
WeirdJoinQueryWhere _where;
|
||||||
|
|
||||||
|
UnorthodoxQuery _unorthodox;
|
||||||
|
|
||||||
|
SongQuery _song;
|
||||||
|
|
||||||
|
NumbaQuery _numbas;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get casts {
|
get casts {
|
||||||
return {};
|
return {};
|
||||||
|
@ -265,6 +283,18 @@ class WeirdJoinQuery extends Query<WeirdJoin, WeirdJoinQueryWhere> {
|
||||||
return parseRow(row);
|
return parseRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UnorthodoxQuery get unorthodox {
|
||||||
|
return _unorthodox;
|
||||||
|
}
|
||||||
|
|
||||||
|
SongQuery get song {
|
||||||
|
return _song;
|
||||||
|
}
|
||||||
|
|
||||||
|
NumbaQuery get numbas {
|
||||||
|
return _numbas;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool canCompile(trampoline) {
|
bool canCompile(trampoline) {
|
||||||
return (!(trampoline.contains('weird_joins') &&
|
return (!(trampoline.contains('weird_joins') &&
|
||||||
|
@ -372,7 +402,7 @@ class WeirdJoinQueryValues extends MapQueryValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SongQuery extends Query<Song, SongQueryWhere> {
|
class SongQuery extends Query<Song, SongQueryWhere> {
|
||||||
SongQuery({Set<String> trampoline}) {
|
SongQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = SongQueryWhere(this);
|
_where = SongQueryWhere(this);
|
||||||
|
@ -489,7 +519,7 @@ class SongQueryValues extends MapQueryValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
class NumbaQuery extends Query<Numba, NumbaQueryWhere> {
|
class NumbaQuery extends Query<Numba, NumbaQueryWhere> {
|
||||||
NumbaQuery({Set<String> trampoline}) {
|
NumbaQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = NumbaQueryWhere(this);
|
_where = NumbaQueryWhere(this);
|
||||||
|
@ -575,12 +605,16 @@ class NumbaQueryValues extends MapQueryValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
class FooQuery extends Query<Foo, FooQueryWhere> {
|
class FooQuery extends Query<Foo, FooQueryWhere> {
|
||||||
FooQuery({Set<String> trampoline}) {
|
FooQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = FooQueryWhere(this);
|
_where = FooQueryWhere(this);
|
||||||
leftJoin(FooPivotQuery(trampoline: trampoline), 'bar', 'foo_bar',
|
leftJoin(
|
||||||
additionalFields: const ['id', 'join_name'], trampoline: trampoline);
|
'(SELECT foo_pivots.foo_bar, weird_joins.id, weird_joins.join_name FROM weird_joins LEFT JOIN foo_pivots ON foo_pivots.weird_join_id=weird_joins.id)',
|
||||||
|
'bar',
|
||||||
|
'foo_bar',
|
||||||
|
additionalFields: const ['id', 'join_name'],
|
||||||
|
trampoline: trampoline);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -723,13 +757,16 @@ class FooQueryValues extends MapQueryValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
class FooPivotQuery extends Query<FooPivot, FooPivotQueryWhere> {
|
class FooPivotQuery extends Query<FooPivot, FooPivotQueryWhere> {
|
||||||
FooPivotQuery({Set<String> trampoline}) {
|
FooPivotQuery({Query parent, Set<String> trampoline})
|
||||||
|
: super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = FooPivotQueryWhere(this);
|
_where = FooPivotQueryWhere(this);
|
||||||
leftJoin('weird_joins', 'weird_join_id', 'id',
|
leftJoin(_weirdJoin = WeirdJoinQuery(trampoline: trampoline, parent: this),
|
||||||
|
'weird_join_id', 'id',
|
||||||
additionalFields: const ['id', 'join_name'], trampoline: trampoline);
|
additionalFields: const ['id', 'join_name'], trampoline: trampoline);
|
||||||
leftJoin('foos', 'foo_bar', 'bar',
|
leftJoin(
|
||||||
|
_foo = FooQuery(trampoline: trampoline, parent: this), 'foo_bar', 'bar',
|
||||||
additionalFields: const ['bar'], trampoline: trampoline);
|
additionalFields: const ['bar'], trampoline: trampoline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -738,6 +775,10 @@ class FooPivotQuery extends Query<FooPivot, FooPivotQueryWhere> {
|
||||||
|
|
||||||
FooPivotQueryWhere _where;
|
FooPivotQueryWhere _where;
|
||||||
|
|
||||||
|
WeirdJoinQuery _weirdJoin;
|
||||||
|
|
||||||
|
FooQuery _foo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get casts {
|
get casts {
|
||||||
return {};
|
return {};
|
||||||
|
@ -781,6 +822,14 @@ class FooPivotQuery extends Query<FooPivot, FooPivotQueryWhere> {
|
||||||
deserialize(List row) {
|
deserialize(List row) {
|
||||||
return parseRow(row);
|
return parseRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WeirdJoinQuery get weirdJoin {
|
||||||
|
return _weirdJoin;
|
||||||
|
}
|
||||||
|
|
||||||
|
FooQuery get foo {
|
||||||
|
return _foo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FooPivotQueryWhere extends QueryWhere {
|
class FooPivotQueryWhere extends QueryWhere {
|
||||||
|
|
|
@ -62,11 +62,14 @@ class RoleMigration extends Migration {
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
class UserQuery extends Query<User, UserQueryWhere> {
|
class UserQuery extends Query<User, UserQueryWhere> {
|
||||||
UserQuery({Set<String> trampoline}) {
|
UserQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = UserQueryWhere(this);
|
_where = UserQueryWhere(this);
|
||||||
leftJoin(RoleUserQuery(trampoline: trampoline), 'id', 'user_id',
|
leftJoin(
|
||||||
|
'(SELECT role_users.user_id, roles.id, roles.created_at, roles.updated_at, roles.name FROM roles LEFT JOIN role_users ON role_users.role_id=roles.id)',
|
||||||
|
'id',
|
||||||
|
'user_id',
|
||||||
additionalFields: const ['id', 'created_at', 'updated_at', 'name'],
|
additionalFields: const ['id', 'created_at', 'updated_at', 'name'],
|
||||||
trampoline: trampoline);
|
trampoline: trampoline);
|
||||||
}
|
}
|
||||||
|
@ -268,14 +271,17 @@ class UserQueryValues extends MapQueryValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoleUserQuery extends Query<RoleUser, RoleUserQueryWhere> {
|
class RoleUserQuery extends Query<RoleUser, RoleUserQueryWhere> {
|
||||||
RoleUserQuery({Set<String> trampoline}) {
|
RoleUserQuery({Query parent, Set<String> trampoline})
|
||||||
|
: super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = RoleUserQueryWhere(this);
|
_where = RoleUserQueryWhere(this);
|
||||||
leftJoin('roles', 'role_id', 'id',
|
leftJoin(_role = RoleQuery(trampoline: trampoline, parent: this), 'role_id',
|
||||||
|
'id',
|
||||||
additionalFields: const ['id', 'created_at', 'updated_at', 'name'],
|
additionalFields: const ['id', 'created_at', 'updated_at', 'name'],
|
||||||
trampoline: trampoline);
|
trampoline: trampoline);
|
||||||
leftJoin('users', 'user_id', 'id',
|
leftJoin(_user = UserQuery(trampoline: trampoline, parent: this), 'user_id',
|
||||||
|
'id',
|
||||||
additionalFields: const [
|
additionalFields: const [
|
||||||
'id',
|
'id',
|
||||||
'created_at',
|
'created_at',
|
||||||
|
@ -292,6 +298,10 @@ class RoleUserQuery extends Query<RoleUser, RoleUserQueryWhere> {
|
||||||
|
|
||||||
RoleUserQueryWhere _where;
|
RoleUserQueryWhere _where;
|
||||||
|
|
||||||
|
RoleQuery _role;
|
||||||
|
|
||||||
|
UserQuery _user;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get casts {
|
get casts {
|
||||||
return {};
|
return {};
|
||||||
|
@ -335,6 +345,14 @@ class RoleUserQuery extends Query<RoleUser, RoleUserQueryWhere> {
|
||||||
deserialize(List row) {
|
deserialize(List row) {
|
||||||
return parseRow(row);
|
return parseRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RoleQuery get role {
|
||||||
|
return _role;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserQuery get user {
|
||||||
|
return _user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoleUserQueryWhere extends QueryWhere {
|
class RoleUserQueryWhere extends QueryWhere {
|
||||||
|
@ -379,11 +397,14 @@ class RoleUserQueryValues extends MapQueryValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoleQuery extends Query<Role, RoleQueryWhere> {
|
class RoleQuery extends Query<Role, RoleQueryWhere> {
|
||||||
RoleQuery({Set<String> trampoline}) {
|
RoleQuery({Query parent, Set<String> trampoline}) : super(parent: parent) {
|
||||||
trampoline ??= Set();
|
trampoline ??= Set();
|
||||||
trampoline.add(tableName);
|
trampoline.add(tableName);
|
||||||
_where = RoleQueryWhere(this);
|
_where = RoleQueryWhere(this);
|
||||||
leftJoin(RoleUserQuery(trampoline: trampoline), 'id', 'role_id',
|
leftJoin(
|
||||||
|
'(SELECT role_users.role_id, users.id, users.created_at, users.updated_at, users.username, users.password, users.email FROM users LEFT JOIN role_users ON role_users.user_id=users.id)',
|
||||||
|
'id',
|
||||||
|
'role_id',
|
||||||
additionalFields: const [
|
additionalFields: const [
|
||||||
'id',
|
'id',
|
||||||
'created_at',
|
'created_at',
|
||||||
|
|
12
angel_orm_test/lib/src/util.dart
Normal file
12
angel_orm_test/lib/src/util.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
|
||||||
|
void printSeparator(String title) {
|
||||||
|
var b = StringBuffer('===' + title.toUpperCase());
|
||||||
|
for (int i = b.length; i < stdout.terminalColumns - 3; i++) {
|
||||||
|
b.write('=');
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
print(magenta.wrap(b.toString()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,20 @@
|
||||||
name: angel_orm_test
|
name: angel_orm_test
|
||||||
publish_to: none
|
publish_to: none
|
||||||
description: Common tests for Angel ORM backends.s
|
description: Common tests for Angel ORM backends.
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.0.0 <3.0.0"
|
sdk: ">=2.0.0 <3.0.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
angel_migration: ^2.0.0-alpha
|
angel_migration:
|
||||||
|
path: ../angel_migration
|
||||||
angel_model: ^1.0.0
|
angel_model: ^1.0.0
|
||||||
angel_orm: ^2.0.0-dev
|
angel_orm: ^2.0.0
|
||||||
angel_serialize: ^2.0.0
|
angel_serialize: ^2.0.0
|
||||||
test: ^1.0.0
|
test: ^1.0.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angel_orm_generator:
|
angel_orm_generator:
|
||||||
path: ../angel_orm_generator
|
path: ../angel_orm_generator
|
||||||
angel_framework: ^2.0.0-alpha
|
angel_framework: ^2.0.0
|
||||||
build_runner: ^1.0.0
|
build_runner: ^1.0.0
|
||||||
|
dependency_overrides:
|
||||||
|
angel_orm:
|
||||||
|
path: ../angel_orm
|
Loading…
Reference in a new issue