Got many to many
This commit is contained in:
parent
261013aa7e
commit
78cd1086c9
14 changed files with 419 additions and 180 deletions
|
@ -146,6 +146,11 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
|||
'This instance does not support creating new WHERE clauses.');
|
||||
}
|
||||
|
||||
/// Determines whether this query can be compiled.
|
||||
///
|
||||
/// Used to prevent ambiguities in joins.
|
||||
bool canCompile(Set<String> trampoline) => true;
|
||||
|
||||
/// Shorthand for calling [where].or with a new [Where] clause.
|
||||
void andWhere(void Function(Where) f) {
|
||||
var w = newWhereClause();
|
||||
|
@ -192,17 +197,55 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
|||
_crossJoin = tableName;
|
||||
}
|
||||
|
||||
String _joinAlias() => 'a${_joins.length}';
|
||||
String _joinAlias(Set<String> trampoline) {
|
||||
int i = _joins.length;
|
||||
|
||||
while (true) {
|
||||
var a = 'a$i';
|
||||
if (trampoline.add(a)) {
|
||||
return a;
|
||||
} else
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
String _compileJoin(tableName, Set<String> trampoline) {
|
||||
if (tableName is String) return tableName;
|
||||
if (tableName is Query) {
|
||||
if (tableName is String)
|
||||
return tableName;
|
||||
else if (tableName is Query) {
|
||||
var c = tableName.compile(trampoline);
|
||||
if (c == null) return c;
|
||||
return '($c)';
|
||||
} else {
|
||||
throw ArgumentError.value(
|
||||
tableName, 'tableName', 'must be a String or Query');
|
||||
}
|
||||
}
|
||||
|
||||
void _makeJoin(
|
||||
tableName,
|
||||
Set<String> trampoline,
|
||||
JoinType type,
|
||||
String localKey,
|
||||
String foreignKey,
|
||||
String op,
|
||||
List<String> additionalFields) {
|
||||
trampoline ??= Set();
|
||||
|
||||
// Pivot tables guard against ambiguous fields by excluding tables
|
||||
// that have already been queried in this scope.
|
||||
if (trampoline.contains(tableName) && trampoline.contains(this.tableName)) {
|
||||
// ex. if we have {roles, role_users}, then don't join "roles" again.
|
||||
return;
|
||||
}
|
||||
|
||||
var to = _compileJoin(tableName, trampoline);
|
||||
if (to != null) {
|
||||
_joins.add(new JoinBuilder(type, this, to, localKey, foreignKey,
|
||||
op: op,
|
||||
alias: _joinAlias(trampoline),
|
||||
additionalFields: additionalFields));
|
||||
}
|
||||
throw ArgumentError.value(
|
||||
tableName, 'tableName', 'must be a String or Query');
|
||||
}
|
||||
|
||||
/// Execute an `INNER JOIN` against another table.
|
||||
|
@ -210,9 +253,8 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
|||
{String op: '=',
|
||||
List<String> additionalFields: const [],
|
||||
Set<String> trampoline}) {
|
||||
_joins.add(new JoinBuilder(JoinType.inner, this,
|
||||
_compileJoin(tableName, trampoline ?? Set()), localKey, foreignKey,
|
||||
op: op, alias: _joinAlias(), additionalFields: additionalFields));
|
||||
_makeJoin(tableName, trampoline, JoinType.inner, localKey, foreignKey, op,
|
||||
additionalFields);
|
||||
}
|
||||
|
||||
/// Execute a `LEFT JOIN` against another table.
|
||||
|
@ -220,9 +262,8 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
|||
{String op: '=',
|
||||
List<String> additionalFields: const [],
|
||||
Set<String> trampoline}) {
|
||||
_joins.add(new JoinBuilder(JoinType.left, this,
|
||||
_compileJoin(tableName, trampoline ?? Set()), localKey, foreignKey,
|
||||
op: op, alias: _joinAlias(), additionalFields: additionalFields));
|
||||
_makeJoin(tableName, trampoline, JoinType.left, localKey, foreignKey, op,
|
||||
additionalFields);
|
||||
}
|
||||
|
||||
/// Execute a `RIGHT JOIN` against another table.
|
||||
|
@ -230,9 +271,8 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
|||
{String op: '=',
|
||||
List<String> additionalFields: const [],
|
||||
Set<String> trampoline}) {
|
||||
_joins.add(new JoinBuilder(JoinType.right, this,
|
||||
_compileJoin(tableName, trampoline ?? Set()), localKey, foreignKey,
|
||||
op: op, alias: _joinAlias(), additionalFields: additionalFields));
|
||||
_makeJoin(tableName, trampoline, JoinType.right, localKey, foreignKey, op,
|
||||
additionalFields);
|
||||
}
|
||||
|
||||
/// Execute a `FULL OUTER JOIN` against another table.
|
||||
|
@ -240,9 +280,8 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
|||
{String op: '=',
|
||||
List<String> additionalFields: const [],
|
||||
Set<String> trampoline}) {
|
||||
_joins.add(new JoinBuilder(JoinType.full, this,
|
||||
_compileJoin(tableName, trampoline ?? Set()), localKey, foreignKey,
|
||||
op: op, alias: _joinAlias(), additionalFields: additionalFields));
|
||||
_makeJoin(tableName, trampoline, JoinType.full, localKey, foreignKey, op,
|
||||
additionalFields);
|
||||
}
|
||||
|
||||
/// Execute a `SELF JOIN`.
|
||||
|
@ -250,9 +289,8 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
|||
{String op: '=',
|
||||
List<String> additionalFields: const [],
|
||||
Set<String> trampoline}) {
|
||||
_joins.add(new JoinBuilder(JoinType.self, this,
|
||||
_compileJoin(tableName, trampoline ?? Set()), localKey, foreignKey,
|
||||
op: op, alias: _joinAlias(), additionalFields: additionalFields));
|
||||
_makeJoin(tableName, trampoline, JoinType.self, localKey, foreignKey, op,
|
||||
additionalFields);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -261,7 +299,8 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
|||
String preamble,
|
||||
bool withFields: true,
|
||||
String fromQuery}) {
|
||||
if (!trampoline.add(tableName)) {
|
||||
// One table MAY appear multiple times in a query.
|
||||
if (!canCompile(trampoline)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -294,7 +333,7 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
|||
if (preamble == null) {
|
||||
if (_crossJoin != null) b.write(' CROSS JOIN $_crossJoin');
|
||||
for (var join in _joins) {
|
||||
var c = join.compile();
|
||||
var c = join.compile(trampoline);
|
||||
if (c != null) b.write(' $c');
|
||||
}
|
||||
}
|
||||
|
@ -565,7 +604,7 @@ class JoinBuilder {
|
|||
return right;
|
||||
}
|
||||
|
||||
String compile() {
|
||||
String compile(Set<String> trampoline) {
|
||||
if (to == null) return null;
|
||||
var b = new StringBuffer();
|
||||
var left = '${from.tableName}.$key';
|
||||
|
|
|
@ -60,16 +60,16 @@ class BelongsTo extends Relationship {
|
|||
const BelongsTo belongsTo = const BelongsTo();
|
||||
|
||||
class ManyToMany extends Relationship {
|
||||
const ManyToMany(
|
||||
final Type through;
|
||||
|
||||
const ManyToMany(this.through,
|
||||
{String localKey: 'id',
|
||||
String foreignKey,
|
||||
String foreignTable,
|
||||
bool cascadeOnDelete: false})
|
||||
: super(RelationshipType.manyToMany,
|
||||
: super(RelationshipType.hasMany, // Many-to-Many is actually just a hasMany
|
||||
localKey: localKey,
|
||||
foreignKey: foreignKey,
|
||||
foreignTable: foreignTable,
|
||||
cascadeOnDelete: cascadeOnDelete == true);
|
||||
}
|
||||
|
||||
const ManyToMany manyToMany = const ManyToMany();
|
||||
|
|
|
@ -105,7 +105,8 @@ Future<OrmBuildContext> buildOrmContext(
|
|||
var foreignKey = cr.peek('foreignKey')?.stringValue;
|
||||
var foreignTable = cr.peek('foreignTable')?.stringValue;
|
||||
var cascadeOnDelete = cr.peek('cascadeOnDelete')?.boolValue == true;
|
||||
OrmBuildContext foreign;
|
||||
var through = cr.peek('through')?.typeValue;
|
||||
OrmBuildContext foreign, throughContext;
|
||||
|
||||
if (foreignTable == null) {
|
||||
// if (!isModelClass(field.type) &&
|
||||
|
@ -137,6 +138,17 @@ Future<OrmBuildContext> buildOrmContext(
|
|||
resolver,
|
||||
autoSnakeCaseNames);
|
||||
|
||||
// Resolve throughType as well
|
||||
if (through != null && through is InterfaceType) {
|
||||
throughContext = await buildOrmContext(
|
||||
through.element,
|
||||
new ConstantReader(const TypeChecker.fromRuntime(Serializable)
|
||||
.firstAnnotationOf(modelType.element)),
|
||||
buildStep,
|
||||
resolver,
|
||||
autoSnakeCaseNames);
|
||||
}
|
||||
|
||||
var ormAnn = const TypeChecker.fromRuntime(Orm)
|
||||
.firstAnnotationOf(modelType.element);
|
||||
|
||||
|
@ -164,12 +176,15 @@ Future<OrmBuildContext> buildOrmContext(
|
|||
foreignKey ??= 'id';
|
||||
}
|
||||
|
||||
var relation = new Relationship(
|
||||
var relation = new RelationshipReader(
|
||||
type,
|
||||
localKey: localKey,
|
||||
foreignKey: foreignKey,
|
||||
foreignTable: foreignTable,
|
||||
cascadeOnDelete: cascadeOnDelete,
|
||||
through: through,
|
||||
foreign: foreign,
|
||||
throughContext: throughContext,
|
||||
);
|
||||
|
||||
if (relation.type == RelationshipType.belongsTo) {
|
||||
|
@ -189,7 +204,6 @@ Future<OrmBuildContext> buildOrmContext(
|
|||
}
|
||||
|
||||
ctx.relations[field.name] = relation;
|
||||
ctx.relationTypes[relation] = foreign;
|
||||
} else {
|
||||
if (column?.type == null)
|
||||
throw 'Cannot infer SQL column type for field "${ctx.buildContext.originalClassName}.${field.name}" with type "${field.type.displayName}".';
|
||||
|
@ -256,8 +270,7 @@ class OrmBuildContext {
|
|||
|
||||
final Map<String, Column> columns = {};
|
||||
final List<FieldElement> effectiveFields = [];
|
||||
final Map<String, Relationship> relations = {};
|
||||
final Map<Relationship, OrmBuildContext> relationTypes = {};
|
||||
final Map<String, RelationshipReader> relations = {};
|
||||
|
||||
OrmBuildContext(this.buildContext, this.ormAnnotation, this.tableName);
|
||||
}
|
||||
|
|
|
@ -211,7 +211,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
|||
RelationshipType.belongsTo,
|
||||
RelationshipType.hasMany
|
||||
].contains(relation.type)) return;
|
||||
var foreign = ctx.relationTypes[relation];
|
||||
var foreign = relation.foreign;
|
||||
var skipToList = refer('row')
|
||||
.property('skip')
|
||||
.call([literalNum(i)])
|
||||
|
@ -234,7 +234,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
|||
var blockStr = block.accept(new DartEmitter());
|
||||
var ifStr = 'if (row.length > $i) { $blockStr }';
|
||||
b.statements.add(new Code(ifStr));
|
||||
i += ctx.relationTypes[relation].effectiveFields.length;
|
||||
i += relation.foreign.effectiveFields.length;
|
||||
});
|
||||
|
||||
b.addExpression(refer('model').returned);
|
||||
|
@ -279,11 +279,14 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
|||
if (relation.type == RelationshipType.belongsTo ||
|
||||
relation.type == RelationshipType.hasOne ||
|
||||
relation.type == RelationshipType.hasMany) {
|
||||
var foreign = ctx.relationTypes[relation];
|
||||
var additionalFields = foreign.effectiveFields
|
||||
var foreign = relation.throughContext ?? relation.foreign;
|
||||
|
||||
// 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))
|
||||
.map((f) => literalString(
|
||||
foreign.buildContext.resolveFieldName(f.name)));
|
||||
.map((f) => literalString(relation.foreign.buildContext
|
||||
.resolveFieldName(f.name)));
|
||||
|
||||
var joinArgs = [relation.localKey, relation.foreignKey]
|
||||
.map(literalString)
|
||||
.toList();
|
||||
|
@ -303,13 +306,43 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
|||
|
||||
b.addExpression(refer('leftJoin').call(joinArgs, {
|
||||
'additionalFields':
|
||||
literalConstList(additionalFields.toList())
|
||||
literalConstList(additionalFields.toList()),
|
||||
'trampoline': refer('trampoline'),
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
// If we have any many-to-many relations, we need to prevent
|
||||
// fetching this table within their joins.
|
||||
var manyToMany = ctx.relations.entries.where((e) => e.value.isManyToMany);
|
||||
|
||||
if (manyToMany.isNotEmpty) {
|
||||
var outExprs = manyToMany.map<Expression>((e) {
|
||||
var foreignTableName = e.value.throughContext.tableName;
|
||||
return CodeExpression(Code('''
|
||||
(!(
|
||||
trampoline.contains('${ctx.tableName}')
|
||||
&& trampoline.contains('$foreignTableName')
|
||||
))
|
||||
'''));
|
||||
});
|
||||
var out = outExprs.reduce((a, b) => a.and(b));
|
||||
|
||||
clazz.methods.add(new Method((b) {
|
||||
b
|
||||
..name = 'canCompile'
|
||||
..annotations.add(refer('override'))
|
||||
..requiredParameters
|
||||
.add(new Parameter((b) => b..name = 'trampoline'))
|
||||
..returns = refer('bool')
|
||||
..body = Block((b) {
|
||||
b.addExpression(out.returned);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
// TODO: Ultimately remove the insert override
|
||||
if (false && ctx.relations.isNotEmpty) {
|
||||
clazz.methods.add(new Method((b) {
|
||||
|
@ -391,10 +424,11 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
|||
var args = <String, Expression>{};
|
||||
|
||||
ctx.relations.forEach((name, relation) {
|
||||
if (false && relation.type == RelationshipType.hasMany) {
|
||||
// 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 = ctx.relationTypes[relation];
|
||||
var foreign = relation.foreign;
|
||||
var queryType = refer(
|
||||
'${foreign.buildContext.modelClassNameRecase.pascalCase}Query');
|
||||
var queryInstance = queryType.newInstance([]);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import 'package:analyzer/dart/constant/value.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:angel_orm/angel_orm.dart';
|
||||
import 'package:source_gen/source_gen.dart';
|
||||
import 'orm_build_context.dart';
|
||||
|
||||
const TypeChecker columnTypeChecker = const TypeChecker.fromRuntime(Column);
|
||||
|
||||
|
@ -19,3 +21,26 @@ class ColumnReader {
|
|||
|
||||
DartObject get defaultValue => reader.peek('defaultValue')?.objectValue;
|
||||
}
|
||||
|
||||
class RelationshipReader {
|
||||
final int type;
|
||||
final String localKey;
|
||||
final String foreignKey;
|
||||
final String foreignTable;
|
||||
final bool cascadeOnDelete;
|
||||
final DartType through;
|
||||
final OrmBuildContext foreign;
|
||||
final OrmBuildContext throughContext;
|
||||
|
||||
const RelationshipReader(this.type,
|
||||
{this.localKey,
|
||||
this.foreignKey,
|
||||
this.foreignTable,
|
||||
this.cascadeOnDelete,
|
||||
this.through,
|
||||
this.foreign,
|
||||
this.throughContext});
|
||||
|
||||
bool get isManyToMany =>
|
||||
type == RelationshipType.hasMany && throughContext != null;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,6 @@ dev_dependencies:
|
|||
collection: ^1.0.0
|
||||
postgres: ^1.0.0
|
||||
test: ^1.0.0
|
||||
# dependency_overrides:
|
||||
# angel_orm:
|
||||
# path: ../angel_orm
|
||||
dependency_overrides:
|
||||
angel_orm:
|
||||
path: ../angel_orm
|
||||
|
|
|
@ -25,7 +25,7 @@ class PostgresExecutor extends QueryExecutor {
|
|||
@override
|
||||
Future<List<List>> query(
|
||||
String tableName, String query, Map<String, dynamic> substitutionValues,
|
||||
[List<String> returningFields]) {
|
||||
[List<String> returningFields]) async {
|
||||
if (returningFields != null) {
|
||||
var fields = returningFields.join(', ');
|
||||
var returning = 'RETURNING $fields';
|
||||
|
@ -37,7 +37,16 @@ class PostgresExecutor extends QueryExecutor {
|
|||
if (substitutionValues.isNotEmpty) print('Values: $substitutionValues');
|
||||
print(substitutionValues.map((k, v) => MapEntry(k, v.runtimeType)));
|
||||
}
|
||||
return connection.query(query, substitutionValues: substitutionValues);
|
||||
|
||||
var rows =
|
||||
await connection.query(query, substitutionValues: substitutionValues);
|
||||
|
||||
if (!Platform.environment.containsKey('STFU')) {
|
||||
print('Got ${rows.length} row(s):');
|
||||
rows.forEach(print);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -1,45 +1,86 @@
|
|||
library angel_orm_generator.test;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:angel_orm/angel_orm.dart';
|
||||
import 'dart:io';
|
||||
import 'package:test/test.dart';
|
||||
import 'models/user.dart';
|
||||
import 'common.dart';
|
||||
|
||||
main() {
|
||||
QueryExecutor executor;
|
||||
PostgresExecutor executor;
|
||||
Role canPub, canSub;
|
||||
User thosakwe;
|
||||
|
||||
Future<void> dumpQuery(String query) async {
|
||||
if (Platform.environment.containsKey('STFU')) return;
|
||||
print('\n');
|
||||
print('==================================================');
|
||||
print(' DUMPING QUERY');
|
||||
print(query);
|
||||
var rows = await executor.connection.query(query);
|
||||
print('\n${rows.length} row(s):');
|
||||
rows.forEach((r) => print(' * $r'));
|
||||
print('==================================================\n\n');
|
||||
}
|
||||
|
||||
setUp(() async {
|
||||
executor = await connectToPostgres(['user', 'role', 'user_role']);
|
||||
|
||||
var canPubQuery = new RoleQuery()..values.name = 'can_pub';
|
||||
var canSubQuery = new RoleQuery()..values.name = 'can_sub';
|
||||
canPub = await canPubQuery.insert(executor);
|
||||
canSub = await canSubQuery.insert(executor);
|
||||
// await dumpQuery("""
|
||||
// WITH roles as
|
||||
// (INSERT INTO roles (name)
|
||||
// VALUES ('pyt')
|
||||
// RETURNING roles.id, roles.name, roles.created_at, roles.updated_at)
|
||||
// SELECT
|
||||
// roles.id, roles.name, roles.created_at, roles.updated_at
|
||||
// FROM roles
|
||||
// LEFT JOIN
|
||||
// (SELECT
|
||||
// role_users.role_id, role_users.user_id,
|
||||
// a0.id, a0.username, a0.password, a0.email, a0.created_at, a0.updated_at
|
||||
// FROM role_users
|
||||
// LEFT JOIN
|
||||
// users a0 ON role_users.user_id=a0.id)
|
||||
// a1 ON roles.id=a1.role_id
|
||||
// """);
|
||||
|
||||
var thosakweQuery = new UserQuery();
|
||||
var canPubQuery = RoleQuery()..values.name = 'can_pub';
|
||||
var canSubQuery = RoleQuery()..values.name = 'can_sub';
|
||||
canPub = await canPubQuery.insert(executor);
|
||||
print('=== CANPUB: ${canPub?.toJson()}');
|
||||
// await dumpQuery(canPubQuery.compile(Set()));
|
||||
canSub = await canSubQuery.insert(executor);
|
||||
print('=== CANSUB: ${canSub?.toJson()}');
|
||||
|
||||
var thosakweQuery = UserQuery();
|
||||
thosakweQuery.values
|
||||
..username = 'thosakwe'
|
||||
..password = 'Hahahahayoureallythoughtiwasstupidenoughtotypethishere'
|
||||
..email = 'thosakwe AT gmail.com';
|
||||
thosakwe = await thosakweQuery.insert(executor);
|
||||
print('=== THOSAKWE: ${thosakwe?.toJson()}');
|
||||
|
||||
// Allow thosakwe to publish...
|
||||
var thosakwePubQuery = new RoleUserQuery();
|
||||
var thosakwePubQuery = RoleUserQuery();
|
||||
thosakwePubQuery.values
|
||||
..userId = int.parse(thosakwe.id)
|
||||
..roleId = int.parse(canPub.id);
|
||||
await thosakwePubQuery.insert(executor);
|
||||
|
||||
// Allow thosakwe to subscribe...
|
||||
var thosakweSubQuery = new RoleUserQuery();
|
||||
var thosakweSubQuery = RoleUserQuery();
|
||||
thosakweSubQuery.values
|
||||
..userId = int.parse(thosakwe.id)
|
||||
..roleId = int.parse(canSub.id);
|
||||
await thosakweSubQuery.insert(executor);
|
||||
|
||||
// Print all users...
|
||||
// await dumpQuery('select * from users;');
|
||||
// await dumpQuery('select * from roles;');
|
||||
// await dumpQuery('select * from role_users;');
|
||||
var query = RoleQuery()..where.id.equals(canPub.idAsInt);
|
||||
await dumpQuery(query.compile(Set()));
|
||||
|
||||
print('\n');
|
||||
print('==================================================');
|
||||
print(' GOOD STUFF BEGINS HERE ');
|
||||
|
@ -47,7 +88,7 @@ main() {
|
|||
});
|
||||
|
||||
Future<User> fetchThosakwe() async {
|
||||
var query = new UserQuery()..where.id.equals(int.parse(thosakwe.id));
|
||||
var query = UserQuery()..where.id.equals(int.parse(thosakwe.id));
|
||||
return await query.getOne(executor);
|
||||
}
|
||||
|
||||
|
@ -60,9 +101,9 @@ main() {
|
|||
|
||||
test('fetch users for role', () async {
|
||||
for (var role in [canPub, canSub]) {
|
||||
var query = new RoleQuery()..where.id.equals(int.parse(role.id));
|
||||
var query = RoleQuery()..where.id.equals(role.idAsInt);
|
||||
var r = await query.getOne(executor);
|
||||
expect(r.users, [thosakwe]);
|
||||
expect(r.users.toList(), [thosakwe]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -35,9 +35,11 @@ class BookQuery extends Query<Book, BookQueryWhere> {
|
|||
trampoline.add(tableName);
|
||||
_where = BookQueryWhere(this);
|
||||
leftJoin('authors', 'author_id', 'id',
|
||||
additionalFields: const ['id', 'name', 'created_at', 'updated_at']);
|
||||
additionalFields: const ['id', 'name', 'created_at', 'updated_at'],
|
||||
trampoline: trampoline);
|
||||
leftJoin('authors', 'partner_author_id', 'id',
|
||||
additionalFields: const ['id', 'name', 'created_at', 'updated_at']);
|
||||
additionalFields: const ['id', 'name', 'created_at', 'updated_at'],
|
||||
trampoline: trampoline);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -32,13 +32,15 @@ class LegQuery extends Query<Leg, LegQueryWhere> {
|
|||
trampoline ??= Set();
|
||||
trampoline.add(tableName);
|
||||
_where = LegQueryWhere(this);
|
||||
leftJoin('feet', 'id', 'leg_id', additionalFields: const [
|
||||
'id',
|
||||
'leg_id',
|
||||
'n_toes',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
]);
|
||||
leftJoin('feet', 'id', 'leg_id',
|
||||
additionalFields: const [
|
||||
'id',
|
||||
'leg_id',
|
||||
'n_toes',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
],
|
||||
trampoline: trampoline);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -36,7 +36,8 @@ class OrderQuery extends Query<Order, OrderQueryWhere> {
|
|||
trampoline.add(tableName);
|
||||
_where = OrderQueryWhere(this);
|
||||
leftJoin('customers', 'customer_id', 'id',
|
||||
additionalFields: const ['id', 'created_at', 'updated_at']);
|
||||
additionalFields: const ['id', 'created_at', 'updated_at'],
|
||||
trampoline: trampoline);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -39,7 +39,8 @@ class TreeQuery extends Query<Tree, TreeQueryWhere> {
|
|||
'common_name',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
]);
|
||||
],
|
||||
trampoline: trampoline);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -14,13 +14,13 @@ abstract class _User extends Model {
|
|||
String get password;
|
||||
String get email;
|
||||
|
||||
@manyToMany
|
||||
@ManyToMany(_RoleUser)
|
||||
List<_Role> get roles;
|
||||
}
|
||||
|
||||
@serializable
|
||||
@orm
|
||||
abstract class _RoleUser extends Model {
|
||||
abstract class _RoleUser {
|
||||
@belongsTo
|
||||
_Role get role;
|
||||
|
||||
|
@ -33,6 +33,6 @@ abstract class _RoleUser extends Model {
|
|||
abstract class _Role extends Model {
|
||||
String name;
|
||||
|
||||
@manyToMany
|
||||
@ManyToMany(_RoleUser)
|
||||
List<_User> get users;
|
||||
}
|
||||
|
|
|
@ -29,9 +29,6 @@ class RoleUserMigration extends Migration {
|
|||
@override
|
||||
up(Schema schema) {
|
||||
schema.create('role_users', (table) {
|
||||
table.serial('id')..primaryKey();
|
||||
table.timeStamp('created_at');
|
||||
table.timeStamp('updated_at');
|
||||
table.integer('role_id').references('roles', 'id');
|
||||
table.integer('user_id').references('users', 'id');
|
||||
});
|
||||
|
@ -69,6 +66,9 @@ class UserQuery extends Query<User, UserQueryWhere> {
|
|||
trampoline ??= Set();
|
||||
trampoline.add(tableName);
|
||||
_where = UserQueryWhere(this);
|
||||
leftJoin(RoleUserQuery(trampoline: trampoline), 'id', 'user_id',
|
||||
additionalFields: const ['id', 'name', 'created_at', 'updated_at'],
|
||||
trampoline: trampoline);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -117,6 +117,12 @@ class UserQuery extends Query<User, UserQueryWhere> {
|
|||
email: (row[3] as String),
|
||||
createdAt: (row[4] as DateTime),
|
||||
updatedAt: (row[5] as DateTime));
|
||||
if (row.length > 6) {
|
||||
model = model.copyWith(
|
||||
roles: [RoleQuery.parseRow(row.skip(6).toList())]
|
||||
.where((x) => x != null)
|
||||
.toList());
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
|
@ -124,6 +130,69 @@ class UserQuery extends Query<User, UserQueryWhere> {
|
|||
deserialize(List row) {
|
||||
return parseRow(row);
|
||||
}
|
||||
|
||||
@override
|
||||
bool canCompile(trampoline) {
|
||||
return (!(trampoline.contains('users') &&
|
||||
trampoline.contains('role_users')));
|
||||
}
|
||||
|
||||
@override
|
||||
get(QueryExecutor executor) {
|
||||
return super.get(executor).then((result) {
|
||||
return result.fold<List<User>>([], (out, model) {
|
||||
var idx = out.indexWhere((m) => m.id == model.id);
|
||||
|
||||
if (idx == -1) {
|
||||
return out..add(model);
|
||||
} else {
|
||||
var l = out[idx];
|
||||
return out
|
||||
..[idx] = l.copyWith(
|
||||
roles: List<_Role>.from(l.roles ?? [])
|
||||
..addAll(model.roles ?? []));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
update(QueryExecutor executor) {
|
||||
return super.update(executor).then((result) {
|
||||
return result.fold<List<User>>([], (out, model) {
|
||||
var idx = out.indexWhere((m) => m.id == model.id);
|
||||
|
||||
if (idx == -1) {
|
||||
return out..add(model);
|
||||
} else {
|
||||
var l = out[idx];
|
||||
return out
|
||||
..[idx] = l.copyWith(
|
||||
roles: List<_Role>.from(l.roles ?? [])
|
||||
..addAll(model.roles ?? []));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
delete(QueryExecutor executor) {
|
||||
return super.delete(executor).then((result) {
|
||||
return result.fold<List<User>>([], (out, model) {
|
||||
var idx = out.indexWhere((m) => m.id == model.id);
|
||||
|
||||
if (idx == -1) {
|
||||
return out..add(model);
|
||||
} else {
|
||||
var l = out[idx];
|
||||
return out
|
||||
..[idx] = l.copyWith(
|
||||
roles: List<_Role>.from(l.roles ?? [])
|
||||
..addAll(model.roles ?? []));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class UserQueryWhere extends QueryWhere {
|
||||
|
@ -204,15 +273,18 @@ class RoleUserQuery extends Query<RoleUser, RoleUserQueryWhere> {
|
|||
trampoline.add(tableName);
|
||||
_where = RoleUserQueryWhere(this);
|
||||
leftJoin('roles', 'role_id', 'id',
|
||||
additionalFields: const ['id', 'name', 'created_at', 'updated_at']);
|
||||
leftJoin('users', 'user_id', 'id', additionalFields: const [
|
||||
'id',
|
||||
'username',
|
||||
'password',
|
||||
'email',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
]);
|
||||
additionalFields: const ['id', 'name', 'created_at', 'updated_at'],
|
||||
trampoline: trampoline);
|
||||
leftJoin('users', 'user_id', 'id',
|
||||
additionalFields: const [
|
||||
'id',
|
||||
'username',
|
||||
'password',
|
||||
'email',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
],
|
||||
trampoline: trampoline);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -232,7 +304,7 @@ class RoleUserQuery extends Query<RoleUser, RoleUserQueryWhere> {
|
|||
|
||||
@override
|
||||
get fields {
|
||||
return const ['id', 'role_id', 'user_id', 'created_at', 'updated_at'];
|
||||
return const ['role_id', 'user_id'];
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -247,15 +319,12 @@ class RoleUserQuery extends Query<RoleUser, RoleUserQueryWhere> {
|
|||
|
||||
static RoleUser parseRow(List row) {
|
||||
if (row.every((x) => x == null)) return null;
|
||||
var model = RoleUser(
|
||||
id: row[0].toString(),
|
||||
createdAt: (row[3] as DateTime),
|
||||
updatedAt: (row[4] as DateTime));
|
||||
if (row.length > 5) {
|
||||
model = model.copyWith(role: RoleQuery.parseRow(row.skip(5).toList()));
|
||||
var model = RoleUser();
|
||||
if (row.length > 2) {
|
||||
model = model.copyWith(role: RoleQuery.parseRow(row.skip(2).toList()));
|
||||
}
|
||||
if (row.length > 9) {
|
||||
model = model.copyWith(user: UserQuery.parseRow(row.skip(9).toList()));
|
||||
if (row.length > 6) {
|
||||
model = model.copyWith(user: UserQuery.parseRow(row.skip(6).toList()));
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
@ -268,25 +337,16 @@ class RoleUserQuery extends Query<RoleUser, RoleUserQueryWhere> {
|
|||
|
||||
class RoleUserQueryWhere extends QueryWhere {
|
||||
RoleUserQueryWhere(RoleUserQuery query)
|
||||
: id = NumericSqlExpressionBuilder<int>(query, 'id'),
|
||||
roleId = NumericSqlExpressionBuilder<int>(query, 'role_id'),
|
||||
userId = NumericSqlExpressionBuilder<int>(query, 'user_id'),
|
||||
createdAt = DateTimeSqlExpressionBuilder(query, 'created_at'),
|
||||
updatedAt = DateTimeSqlExpressionBuilder(query, 'updated_at');
|
||||
|
||||
final NumericSqlExpressionBuilder<int> id;
|
||||
: roleId = NumericSqlExpressionBuilder<int>(query, 'role_id'),
|
||||
userId = NumericSqlExpressionBuilder<int>(query, 'user_id');
|
||||
|
||||
final NumericSqlExpressionBuilder<int> roleId;
|
||||
|
||||
final NumericSqlExpressionBuilder<int> userId;
|
||||
|
||||
final DateTimeSqlExpressionBuilder createdAt;
|
||||
|
||||
final DateTimeSqlExpressionBuilder updatedAt;
|
||||
|
||||
@override
|
||||
get expressionBuilders {
|
||||
return [id, roleId, userId, createdAt, updatedAt];
|
||||
return [roleId, userId];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,11 +356,6 @@ class RoleUserQueryValues extends MapQueryValues {
|
|||
return {};
|
||||
}
|
||||
|
||||
int get id {
|
||||
return (values['id'] as int);
|
||||
}
|
||||
|
||||
set id(int value) => values['id'] = value;
|
||||
int get roleId {
|
||||
return (values['role_id'] as int);
|
||||
}
|
||||
|
@ -311,19 +366,7 @@ class RoleUserQueryValues extends MapQueryValues {
|
|||
}
|
||||
|
||||
set userId(int value) => values['user_id'] = value;
|
||||
DateTime get createdAt {
|
||||
return (values['created_at'] as DateTime);
|
||||
}
|
||||
|
||||
set createdAt(DateTime value) => values['created_at'] = value;
|
||||
DateTime get updatedAt {
|
||||
return (values['updated_at'] as DateTime);
|
||||
}
|
||||
|
||||
set updatedAt(DateTime value) => values['updated_at'] = value;
|
||||
void copyFrom(RoleUser model) {
|
||||
createdAt = model.createdAt;
|
||||
updatedAt = model.updatedAt;
|
||||
if (model.role != null) {
|
||||
values['role_id'] = int.parse(model.role.id);
|
||||
}
|
||||
|
@ -338,6 +381,16 @@ class RoleQuery extends Query<Role, RoleQueryWhere> {
|
|||
trampoline ??= Set();
|
||||
trampoline.add(tableName);
|
||||
_where = RoleQueryWhere(this);
|
||||
leftJoin(RoleUserQuery(trampoline: trampoline), 'id', 'role_id',
|
||||
additionalFields: const [
|
||||
'id',
|
||||
'username',
|
||||
'password',
|
||||
'email',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
],
|
||||
trampoline: trampoline);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -377,6 +430,12 @@ class RoleQuery extends Query<Role, RoleQueryWhere> {
|
|||
name: (row[1] as String),
|
||||
createdAt: (row[2] as DateTime),
|
||||
updatedAt: (row[3] as DateTime));
|
||||
if (row.length > 4) {
|
||||
model = model.copyWith(
|
||||
users: [UserQuery.parseRow(row.skip(4).toList())]
|
||||
.where((x) => x != null)
|
||||
.toList());
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
|
@ -384,6 +443,69 @@ class RoleQuery extends Query<Role, RoleQueryWhere> {
|
|||
deserialize(List row) {
|
||||
return parseRow(row);
|
||||
}
|
||||
|
||||
@override
|
||||
bool canCompile(trampoline) {
|
||||
return (!(trampoline.contains('roles') &&
|
||||
trampoline.contains('role_users')));
|
||||
}
|
||||
|
||||
@override
|
||||
get(QueryExecutor executor) {
|
||||
return super.get(executor).then((result) {
|
||||
return result.fold<List<Role>>([], (out, model) {
|
||||
var idx = out.indexWhere((m) => m.id == model.id);
|
||||
|
||||
if (idx == -1) {
|
||||
return out..add(model);
|
||||
} else {
|
||||
var l = out[idx];
|
||||
return out
|
||||
..[idx] = l.copyWith(
|
||||
users: List<_User>.from(l.users ?? [])
|
||||
..addAll(model.users ?? []));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
update(QueryExecutor executor) {
|
||||
return super.update(executor).then((result) {
|
||||
return result.fold<List<Role>>([], (out, model) {
|
||||
var idx = out.indexWhere((m) => m.id == model.id);
|
||||
|
||||
if (idx == -1) {
|
||||
return out..add(model);
|
||||
} else {
|
||||
var l = out[idx];
|
||||
return out
|
||||
..[idx] = l.copyWith(
|
||||
users: List<_User>.from(l.users ?? [])
|
||||
..addAll(model.users ?? []));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
delete(QueryExecutor executor) {
|
||||
return super.delete(executor).then((result) {
|
||||
return result.fold<List<Role>>([], (out, model) {
|
||||
var idx = out.indexWhere((m) => m.id == model.id);
|
||||
|
||||
if (idx == -1) {
|
||||
return out..add(model);
|
||||
} else {
|
||||
var l = out[idx];
|
||||
return out
|
||||
..[idx] = l.copyWith(
|
||||
users: List<_User>.from(l.users ?? [])
|
||||
..addAll(model.users ?? []));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class RoleQueryWhere extends QueryWhere {
|
||||
|
@ -519,11 +641,8 @@ class User extends _User {
|
|||
}
|
||||
|
||||
@generatedSerializable
|
||||
class RoleUser extends _RoleUser {
|
||||
RoleUser({this.id, this.role, this.user, this.createdAt, this.updatedAt});
|
||||
|
||||
@override
|
||||
final String id;
|
||||
class RoleUser implements _RoleUser {
|
||||
const RoleUser({this.role, this.user});
|
||||
|
||||
@override
|
||||
final _Role role;
|
||||
|
@ -531,38 +650,17 @@ class RoleUser extends _RoleUser {
|
|||
@override
|
||||
final _User user;
|
||||
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
@override
|
||||
final DateTime updatedAt;
|
||||
|
||||
RoleUser copyWith(
|
||||
{String id,
|
||||
_Role role,
|
||||
_User user,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt}) {
|
||||
return new RoleUser(
|
||||
id: id ?? this.id,
|
||||
role: role ?? this.role,
|
||||
user: user ?? this.user,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt);
|
||||
RoleUser copyWith({_Role role, _User user}) {
|
||||
return new RoleUser(role: role ?? this.role, user: user ?? this.user);
|
||||
}
|
||||
|
||||
bool operator ==(other) {
|
||||
return other is _RoleUser &&
|
||||
other.id == id &&
|
||||
other.role == role &&
|
||||
other.user == user &&
|
||||
other.createdAt == createdAt &&
|
||||
other.updatedAt == updatedAt;
|
||||
return other is _RoleUser && other.role == role && other.user == user;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return hashObjects([id, role, user, createdAt, updatedAt]);
|
||||
return hashObjects([role, user]);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
|
@ -698,22 +796,11 @@ abstract class UserFields {
|
|||
abstract class RoleUserSerializer {
|
||||
static RoleUser fromMap(Map map) {
|
||||
return new RoleUser(
|
||||
id: map['id'] as String,
|
||||
role: map['role'] != null
|
||||
? RoleSerializer.fromMap(map['role'] as Map)
|
||||
: null,
|
||||
user: map['user'] != null
|
||||
? UserSerializer.fromMap(map['user'] as Map)
|
||||
: null,
|
||||
createdAt: map['created_at'] != null
|
||||
? (map['created_at'] is DateTime
|
||||
? (map['created_at'] as DateTime)
|
||||
: DateTime.parse(map['created_at'].toString()))
|
||||
: null,
|
||||
updatedAt: map['updated_at'] != null
|
||||
? (map['updated_at'] is DateTime
|
||||
? (map['updated_at'] as DateTime)
|
||||
: DateTime.parse(map['updated_at'].toString()))
|
||||
: null);
|
||||
}
|
||||
|
||||
|
@ -722,33 +809,18 @@ abstract class RoleUserSerializer {
|
|||
return null;
|
||||
}
|
||||
return {
|
||||
'id': model.id,
|
||||
'role': RoleSerializer.toMap(model.role),
|
||||
'user': UserSerializer.toMap(model.user),
|
||||
'created_at': model.createdAt?.toIso8601String(),
|
||||
'updated_at': model.updatedAt?.toIso8601String()
|
||||
'user': UserSerializer.toMap(model.user)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
abstract class RoleUserFields {
|
||||
static const List<String> allFields = <String>[
|
||||
id,
|
||||
role,
|
||||
user,
|
||||
createdAt,
|
||||
updatedAt
|
||||
];
|
||||
|
||||
static const String id = 'id';
|
||||
static const List<String> allFields = <String>[role, user];
|
||||
|
||||
static const String role = 'role';
|
||||
|
||||
static const String user = 'user';
|
||||
|
||||
static const String createdAt = 'created_at';
|
||||
|
||||
static const String updatedAt = 'updated_at';
|
||||
}
|
||||
|
||||
abstract class RoleSerializer {
|
||||
|
|
Loading…
Reference in a new issue