Got many to many

This commit is contained in:
Tobe O 2019-04-02 19:05:13 -04:00
parent 261013aa7e
commit 78cd1086c9
14 changed files with 419 additions and 180 deletions

View file

@ -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,27 +197,64 @@ 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));
}
}
/// Execute an `INNER JOIN` against another table.
void join(tableName, String localKey, String foreignKey,
{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';

View file

@ -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();

View file

@ -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);
}

View file

@ -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([]);

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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]);
}
});
}

View file

@ -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

View file

@ -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 [
leftJoin('feet', 'id', 'leg_id',
additionalFields: const [
'id',
'leg_id',
'n_toes',
'created_at',
'updated_at'
]);
],
trampoline: trampoline);
}
@override

View file

@ -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

View file

@ -39,7 +39,8 @@ class TreeQuery extends Query<Tree, TreeQueryWhere> {
'common_name',
'created_at',
'updated_at'
]);
],
trampoline: trampoline);
}
@override

View file

@ -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;
}

View file

@ -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 [
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 {