diff --git a/angel_orm/lib/src/query.dart b/angel_orm/lib/src/query.dart index aced78f3..03150216 100644 --- a/angel_orm/lib/src/query.dart +++ b/angel_orm/lib/src/query.dart @@ -229,7 +229,10 @@ abstract class Query extends QueryBase { @override String compile( - {bool includeTableName: false, String preamble, bool withFields: true}) { + {bool includeTableName: false, + String preamble, + bool withFields: true, + String fromQuery}) { includeTableName = includeTableName || _joins.isNotEmpty; var b = new StringBuffer(preamble ?? 'SELECT'); b.write(' '); @@ -247,7 +250,8 @@ abstract class Query extends QueryBase { }); } if (withFields) b.write(f.join(', ')); - b.write(' FROM $tableName'); + fromQuery ??= tableName; + b.write(' FROM $fromQuery'); // No joins if it's not a select. if (preamble == null) { @@ -267,7 +271,7 @@ abstract class Query extends QueryBase { @override Future getOne(QueryExecutor executor) { - limit(1); + //limit(1); return super.getOne(executor); } @@ -294,42 +298,40 @@ abstract class Query extends QueryBase { } Future insert(QueryExecutor executor) { - var sql = values.compileInsert(this, tableName); + var insertion = values.compileInsert(this, tableName); - if (sql == null) { + if (insertion == null) { throw new StateError('No values have been specified for update.'); } else { + // TODO: How to do this in a non-Postgres DB? + var returning = fields.map(adornWithTableName).join(', '); + var sql = compile(); + sql = 'WITH $tableName as ($insertion RETURNING $returning) ' + sql; return executor - .query( - sql, substitutionValues, fields.map(adornWithTableName).toList()) + .query(sql, substitutionValues) .then((it) => it.isEmpty ? null : deserialize(it.first)); } } Future> update(QueryExecutor executor) async { - var sql = new StringBuffer('UPDATE $tableName '); + var updateSql = new StringBuffer('UPDATE $tableName '); var valuesClause = values.compileForUpdate(this); if (valuesClause == null) { throw new StateError('No values have been specified for update.'); } else { - sql.write(' $valuesClause'); + updateSql.write(' $valuesClause'); var whereClause = where.compile(); - if (whereClause.isNotEmpty) sql.write(' WHERE $whereClause'); - if (_limit != null) sql.write(' LIMIT $_limit'); + if (whereClause.isNotEmpty) updateSql.write(' WHERE $whereClause'); + if (_limit != null) updateSql.write(' LIMIT $_limit'); - if (_joins.isEmpty) { - return executor - .query(sql.toString(), substitutionValues, - fields.map(adornWithTableName).toList()) - .then((it) => it.map(deserialize).toList()); - } else { - // TODO: Can this be done with just *one* query? - return executor - .query(sql.toString(), substitutionValues, - fields.map(adornWithTableName).toList()) - .then((it) => get(executor)); - } + var returning = fields.map(adornWithTableName).join(', '); + var sql = compile(); + sql = 'WITH $tableName as ($updateSql RETURNING $returning) ' + sql; + + return executor + .query(sql, substitutionValues) + .then((it) => it.map(deserialize).toList()); } } diff --git a/angel_orm/lib/src/relations.dart b/angel_orm/lib/src/relations.dart index bf51718d..6f6be8f7 100644 --- a/angel_orm/lib/src/relations.dart +++ b/angel_orm/lib/src/relations.dart @@ -2,6 +2,7 @@ abstract class RelationshipType { static const int hasMany = 0; static const int hasOne = 1; static const int belongsTo = 2; + static const int manyToMany = 3; } class Relationship { @@ -57,3 +58,18 @@ class BelongsTo extends Relationship { } const BelongsTo belongsTo = const BelongsTo(); + +class ManyToMany extends Relationship { + const ManyToMany( + {String localKey: 'id', + String foreignKey, + String foreignTable, + bool cascadeOnDelete: false}) + : super(RelationshipType.manyToMany, + localKey: localKey, + foreignKey: foreignKey, + foreignTable: foreignTable, + cascadeOnDelete: cascadeOnDelete == true); +} + +const ManyToMany manyToMany = const ManyToMany(); diff --git a/angel_orm_generator/lib/src/orm_generator.dart b/angel_orm_generator/lib/src/orm_generator.dart index 52d6eadd..dea1b079 100644 --- a/angel_orm_generator/lib/src/orm_generator.dart +++ b/angel_orm_generator/lib/src/orm_generator.dart @@ -165,8 +165,11 @@ class OrmGenerator extends GeneratorForAnnotation { .newInstance([], args).assignVar('model')); ctx.relations.forEach((name, relation) { - if (!const [RelationshipType.hasOne, RelationshipType.belongsTo] - .contains(relation.type)) return; + if (!const [ + RelationshipType.hasOne, + RelationshipType.belongsTo, + RelationshipType.hasMany + ].contains(relation.type)) return; var foreign = ctx.relationTypes[relation]; var skipToList = refer('row') .property('skip') @@ -177,6 +180,12 @@ class OrmGenerator extends GeneratorForAnnotation { '${foreign.buildContext.modelClassNameRecase.pascalCase}Query') .property('parseRow') .call([skipToList]); + if (relation.type == RelationshipType.hasMany) { + parsed = literalList([parsed]); + var pp = parsed.accept(DartEmitter()); + parsed = CodeExpression( + Code('$pp.where((x) => x != null).toList()')); + } var expr = refer('model').property('copyWith').call([], {name: parsed}); var block = new Block( @@ -216,17 +225,36 @@ class OrmGenerator extends GeneratorForAnnotation { ctx.relations.forEach((fieldName, relation) { //var name = ctx.buildContext.resolveFieldName(fieldName); if (relation.type == RelationshipType.belongsTo || - relation.type == RelationshipType.hasOne) { + relation.type == RelationshipType.hasOne || + relation.type == RelationshipType.hasMany) { var foreign = ctx.relationTypes[relation]; var additionalFields = foreign.effectiveFields .where((f) => f.name != 'id' || !isSpecialId(f)) .map((f) => literalString( foreign.buildContext.resolveFieldName(f.name))); - var joinArgs = [ - relation.foreignTable, - relation.localKey, - relation.foreignKey - ].map(literalString); + var joinArgs = [relation.localKey, relation.foreignKey] + .map(literalString) + .toList(); + + // Instead of passing the table as-is, we'll compile a subquery. + if (relation.type == RelationshipType.hasMany) { + var foreignQueryType = + foreign.buildContext.modelClassNameRecase.pascalCase + + 'Query'; + var compiledSubquery = refer(foreignQueryType) + .newInstance([]) + .property('compile') + .call([]); + + joinArgs.insert( + 0, + literalString('(') + .operatorAdd(compiledSubquery) + .operatorAdd(literalString(')'))); + } else { + joinArgs.insert(0, literalString(foreign.tableName)); + } + b.addExpression(refer('leftJoin').call(joinArgs, { 'additionalFields': literalConstList(additionalFields.toList()) @@ -235,7 +263,9 @@ class OrmGenerator extends GeneratorForAnnotation { }); }); })); - if (ctx.relations.isNotEmpty) { + + // TODO: Ultimately remove the insert override + if (false && ctx.relations.isNotEmpty) { clazz.methods.add(new Method((b) { b ..name = 'insert' @@ -270,15 +300,15 @@ class OrmGenerator extends GeneratorForAnnotation { refer('getOne').call([refer('executor')]).awaited)); } - // 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)); - } - }); + // 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); }); @@ -292,7 +322,8 @@ class OrmGenerator extends GeneratorForAnnotation { } // Create a Future fetchLinked(T model, QueryExecutor), if necessary. - if (ctx.relations.values.any((r) => r.type == RelationshipType.hasMany)) { + if (false && + ctx.relations.values.any((r) => r.type == RelationshipType.hasMany)) { clazz.methods.add(new Method((b) { b ..name = 'fetchLinked' @@ -314,7 +345,7 @@ class OrmGenerator extends GeneratorForAnnotation { var args = {}; ctx.relations.forEach((name, relation) { - if (relation.type == RelationshipType.hasMany) { + if (false && relation.type == RelationshipType.hasMany) { // For each hasMany, we need to create a query of // the corresponding type. var foreign = ctx.relationTypes[relation]; @@ -368,29 +399,46 @@ class OrmGenerator extends GeneratorForAnnotation { if (ctx.relations.values.any((r) => r.type == RelationshipType.hasMany)) { for (var methodName in const ['get', 'update', 'delete']) { clazz.methods.add(new Method((b) { + var type = ctx.buildContext.modelClassType.accept(DartEmitter()); b ..name = methodName ..annotations.add(refer('override')) ..requiredParameters.add(new Parameter((b) => b ..name = 'executor' - ..type = refer('QueryExecutor'))) - ..body = new Block((b) { - var inTransaction = new Method((b) { - b - ..modifier = MethodModifier.async - ..body = new Block((b) { - var mapped = new CodeExpression(new Code( - 'await Future.wait(result.map((m) => fetchLinked(m, executor)))')); - b.addExpression(new CodeExpression(new Code( - 'var result = await super.$methodName(executor)'))); - b.addExpression(mapped.returned); - }); - }); + ..type = refer('QueryExecutor'))); - b.addExpression(refer('executor') - .property('transaction') - .call([inTransaction.closure]).returned); - }); + // Collect hasMany options, and ultimately merge them + var merge = []; + + ctx.relations.forEach((name, relation) { + if (relation.type == RelationshipType.hasMany) { + // This is only allowed with lists. + var field = + ctx.buildContext.fields.firstWhere((f) => f.name == name); + var typeLiteral = + convertTypeReference(field.type).accept(DartEmitter()); + merge.add(''' + $name: $typeLiteral.from(l.$name ?? [])..addAll(model.$name ?? []) + '''); + } + }); + + var merged = merge.join(', '); + + b.body = new Code(''' + return super.$methodName(executor).then((result) { + return result.fold>([], (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($merged); + } + }); + }); + '''); })); } } diff --git a/angel_orm_generator/pubspec.yaml b/angel_orm_generator/pubspec.yaml index 13b90539..b29016fd 100644 --- a/angel_orm_generator/pubspec.yaml +++ b/angel_orm_generator/pubspec.yaml @@ -34,6 +34,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 diff --git a/angel_orm_generator/test/has_one_test.dart b/angel_orm_generator/test/has_one_test.dart index 2879569c..9ab6d798 100644 --- a/angel_orm_generator/test/has_one_test.dart +++ b/angel_orm_generator/test/has_one_test.dart @@ -8,7 +8,7 @@ import 'models/leg.dart'; import 'common.dart'; main() { - QueryExecutor executor; + PostgresExecutor executor; Leg originalLeg; setUp(() async { diff --git a/angel_orm_generator/test/many_to_many_test.dart b/angel_orm_generator/test/many_to_many_test.dart index cb4690b3..0a82be59 100644 --- a/angel_orm_generator/test/many_to_many_test.dart +++ b/angel_orm_generator/test/many_to_many_test.dart @@ -27,18 +27,23 @@ main() { thosakwe = await thosakweQuery.insert(executor); // Allow thosakwe to publish... - var thosakwePubQuery = new UserRoleQuery(); + var thosakwePubQuery = new RoleUserQuery(); thosakwePubQuery.values ..userId = int.parse(thosakwe.id) ..roleId = int.parse(canPub.id); await thosakwePubQuery.insert(executor); // Allow thosakwe to subscribe... - var thosakweSubQuery = new UserRoleQuery(); + var thosakweSubQuery = new RoleUserQuery(); thosakweSubQuery.values ..userId = int.parse(thosakwe.id) ..roleId = int.parse(canSub.id); await thosakweSubQuery.insert(executor); + + print('\n'); + print('=================================================='); + print(' GOOD STUFF BEGINS HERE '); + print('==================================================\n\n'); }); Future fetchThosakwe() async { diff --git a/angel_orm_generator/test/models/book.g.dart b/angel_orm_generator/test/models/book.g.dart index 3805842b..69e23d31 100644 --- a/angel_orm_generator/test/models/book.g.dart +++ b/angel_orm_generator/test/models/book.g.dart @@ -92,16 +92,6 @@ class BookQuery extends Query { deserialize(List row) { return parseRow(row); } - - @override - insert(executor) { - return executor.transaction(() async { - var result = await super.insert(executor); - where.id.equals(int.parse(result.id)); - result = await getOne(executor); - return result; - }); - } } class BookQueryWhere extends QueryWhere { diff --git a/angel_orm_generator/test/models/leg.g.dart b/angel_orm_generator/test/models/leg.g.dart index c8e67054..1c2ffe61 100644 --- a/angel_orm_generator/test/models/leg.g.dart +++ b/angel_orm_generator/test/models/leg.g.dart @@ -80,16 +80,6 @@ class LegQuery extends Query { deserialize(List row) { return parseRow(row); } - - @override - insert(executor) { - return executor.transaction(() async { - var result = await super.insert(executor); - where.id.equals(int.parse(result.id)); - result = await getOne(executor); - return result; - }); - } } class LegQueryWhere extends QueryWhere { diff --git a/angel_orm_generator/test/models/tree.g.dart b/angel_orm_generator/test/models/tree.g.dart index 78390ccd..31b8690a 100644 --- a/angel_orm_generator/test/models/tree.g.dart +++ b/angel_orm_generator/test/models/tree.g.dart @@ -30,6 +30,13 @@ class TreeMigration extends Migration { class TreeQuery extends Query { TreeQuery() { _where = new TreeQueryWhere(this); + leftJoin('(' + new FruitQuery().compile() + ')', 'id', 'tree_id', + additionalFields: const [ + 'tree_id', + 'common_name', + 'created_at', + 'updated_at' + ]); } @override @@ -64,6 +71,12 @@ class TreeQuery extends Query { rings: (row[1] as int), createdAt: (row[2] as DateTime), updatedAt: (row[3] as DateTime)); + if (row.length > 4) { + model = model.copyWith( + fruits: [FruitQuery.parseRow(row.skip(4).toList())] + .where((x) => x != null) + .toList()); + } return model; } @@ -72,45 +85,60 @@ class TreeQuery extends Query { return parseRow(row); } - @override - insert(executor) { - return executor.transaction(() async { - var result = await super.insert(executor); - where.id.equals(int.parse(result.id)); - result = await getOne(executor); - result = await fetchLinked(result, executor); - return result; - }); - } - - Future fetchLinked(Tree model, QueryExecutor executor) async { - return model.copyWith( - fruits: await (new FruitQuery() - ..where.treeId.equals(int.parse(model.id))) - .get(executor)); - } - @override get(QueryExecutor executor) { - return executor.transaction(() async { - var result = await super.get(executor); - return await Future.wait(result.map((m) => fetchLinked(m, executor))); + return super.get(executor).then((result) { + return result.fold>([], (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( + fruits: List.from(l.fruits ?? []) + ..addAll(model.fruits ?? [])); + } + }); }); } @override update(QueryExecutor executor) { - return executor.transaction(() async { - var result = await super.update(executor); - return await Future.wait(result.map((m) => fetchLinked(m, executor))); + return super.update(executor).then((result) { + return result.fold>([], (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( + fruits: List.from(l.fruits ?? []) + ..addAll(model.fruits ?? [])); + } + }); }); } @override delete(QueryExecutor executor) { - return executor.transaction(() async { - var result = await super.delete(executor); - return await Future.wait(result.map((m) => fetchLinked(m, executor))); + return super.delete(executor).then((result) { + return result.fold>([], (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( + fruits: List.from(l.fruits ?? []) + ..addAll(model.fruits ?? [])); + } + }); }); } } diff --git a/angel_orm_generator/test/models/user.dart b/angel_orm_generator/test/models/user.dart index 2ba02c00..af714694 100644 --- a/angel_orm_generator/test/models/user.dart +++ b/angel_orm_generator/test/models/user.dart @@ -14,10 +14,18 @@ abstract class _User extends Model { String get password; String get email; - @hasMany - List<_UserRole> get userRoles; + @manyToMany + List<_Role> get roles; +} - List<_Role> get roles => userRoles.map((m) => m.role).toList(); +@serializable +@orm +abstract class _RoleUser extends Model { + @belongsTo + _Role get role; + + @belongsTo + _User get user; } @serializable @@ -25,20 +33,6 @@ abstract class _User extends Model { abstract class _Role extends Model { String name; - @hasMany - List<_UserRole> get userRoles; - - List<_User> get users => userRoles.map((m) => m.user).toList(); -} - -@Serializable(autoIdAndDateFields: false) -@orm -abstract class _UserRole { - int get id; - - @belongsTo - _User get user; - - @belongsTo - _Role get role; + @manyToMany + List<_User> get users; } diff --git a/angel_orm_generator/test/models/user.g.dart b/angel_orm_generator/test/models/user.g.dart index 35e5052e..81ea49e6 100644 --- a/angel_orm_generator/test/models/user.g.dart +++ b/angel_orm_generator/test/models/user.g.dart @@ -25,6 +25,24 @@ class UserMigration extends Migration { } } +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'); + }); + } + + @override + down(Schema schema) { + schema.drop('role_users'); + } +} + class RoleMigration extends Migration { @override up(Schema schema) { @@ -42,22 +60,6 @@ class RoleMigration extends Migration { } } -class UserRoleMigration extends Migration { - @override - up(Schema schema) { - schema.create('user_roles', (table) { - table.serial('id')..primaryKey(); - table.integer('user_id').references('users', 'id'); - table.integer('role_id').references('roles', 'id'); - }); - } - - @override - down(Schema schema) { - schema.drop('user_roles'); - } -} - // ************************************************************************** // OrmGenerator // ************************************************************************** @@ -115,48 +117,6 @@ class UserQuery extends Query { deserialize(List row) { return parseRow(row); } - - @override - insert(executor) { - return executor.transaction(() async { - var result = await super.insert(executor); - where.id.equals(int.parse(result.id)); - result = await getOne(executor); - result = await fetchLinked(result, executor); - return result; - }); - } - - Future fetchLinked(User model, QueryExecutor executor) async { - return model.copyWith( - userRoles: await (new UserRoleQuery() - ..where.userId.equals(int.parse(model.id))) - .get(executor)); - } - - @override - get(QueryExecutor executor) { - return executor.transaction(() async { - var result = await super.get(executor); - return await Future.wait(result.map((m) => fetchLinked(m, executor))); - }); - } - - @override - update(QueryExecutor executor) { - return executor.transaction(() async { - var result = await super.update(executor); - return await Future.wait(result.map((m) => fetchLinked(m, executor))); - }); - } - - @override - delete(QueryExecutor executor) { - return executor.transaction(() async { - var result = await super.delete(executor); - return await Future.wait(result.map((m) => fetchLinked(m, executor))); - }); - } } class UserQueryWhere extends QueryWhere { @@ -228,6 +188,128 @@ class UserQueryValues extends MapQueryValues { } } +class RoleUserQuery extends Query { + RoleUserQuery() { + _where = new RoleUserQueryWhere(this); + leftJoin('roles', 'role_id', 'id', + additionalFields: const ['name', 'created_at', 'updated_at']); + leftJoin('users', 'user_id', 'id', additionalFields: const [ + 'username', + 'password', + 'email', + 'created_at', + 'updated_at' + ]); + } + + @override + final RoleUserQueryValues values = new RoleUserQueryValues(); + + RoleUserQueryWhere _where; + + @override + get tableName { + return 'role_users'; + } + + @override + get fields { + return const ['id', 'role_id', 'user_id', 'created_at', 'updated_at']; + } + + @override + RoleUserQueryWhere get where { + return _where; + } + + @override + RoleUserQueryWhere newWhereClause() { + return new RoleUserQueryWhere(this); + } + + static RoleUser parseRow(List row) { + if (row.every((x) => x == null)) return null; + var model = new 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())); + } + if (row.length > 9) { + model = model.copyWith(user: UserQuery.parseRow(row.skip(9).toList())); + } + return model; + } + + @override + deserialize(List row) { + return parseRow(row); + } +} + +class RoleUserQueryWhere extends QueryWhere { + RoleUserQueryWhere(RoleUserQuery query) + : id = new NumericSqlExpressionBuilder(query, 'id'), + roleId = new NumericSqlExpressionBuilder(query, 'role_id'), + userId = new NumericSqlExpressionBuilder(query, 'user_id'), + createdAt = new DateTimeSqlExpressionBuilder(query, 'created_at'), + updatedAt = new DateTimeSqlExpressionBuilder(query, 'updated_at'); + + final NumericSqlExpressionBuilder id; + + final NumericSqlExpressionBuilder roleId; + + final NumericSqlExpressionBuilder userId; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + @override + get expressionBuilders { + return [id, roleId, userId, createdAt, updatedAt]; + } +} + +class RoleUserQueryValues extends MapQueryValues { + int get id { + return (values['id'] as int); + } + + set id(int value) => values['id'] = value; + int get roleId { + return (values['role_id'] as int); + } + + set roleId(int value) => values['role_id'] = value; + int get userId { + return (values['user_id'] as int); + } + + 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) { + values + .addAll({'created_at': model.createdAt, 'updated_at': model.updatedAt}); + if (model.role != null) { + values['role_id'] = int.parse(model.role.id); + } + if (model.user != null) { + values['user_id'] = int.parse(model.user.id); + } + } +} + class RoleQuery extends Query { RoleQuery() { _where = new RoleQueryWhere(this); @@ -272,48 +354,6 @@ class RoleQuery extends Query { deserialize(List row) { return parseRow(row); } - - @override - insert(executor) { - return executor.transaction(() async { - var result = await super.insert(executor); - where.id.equals(int.parse(result.id)); - result = await getOne(executor); - result = await fetchLinked(result, executor); - return result; - }); - } - - Future fetchLinked(Role model, QueryExecutor executor) async { - return model.copyWith( - userRoles: await (new UserRoleQuery() - ..where.roleId.equals(int.parse(model.id))) - .get(executor)); - } - - @override - get(QueryExecutor executor) { - return executor.transaction(() async { - var result = await super.get(executor); - return await Future.wait(result.map((m) => fetchLinked(m, executor))); - }); - } - - @override - update(QueryExecutor executor) { - return executor.transaction(() async { - var result = await super.update(executor); - return await Future.wait(result.map((m) => fetchLinked(m, executor))); - }); - } - - @override - delete(QueryExecutor executor) { - return executor.transaction(() async { - var result = await super.delete(executor); - return await Future.wait(result.map((m) => fetchLinked(m, executor))); - }); - } } class RoleQueryWhere extends QueryWhere { @@ -367,116 +407,6 @@ class RoleQueryValues extends MapQueryValues { } } -class UserRoleQuery extends Query { - UserRoleQuery() { - _where = new UserRoleQueryWhere(this); - leftJoin('users', 'user_id', 'id', additionalFields: const [ - 'username', - 'password', - 'email', - 'created_at', - 'updated_at' - ]); - leftJoin('roles', 'role_id', 'id', - additionalFields: const ['name', 'created_at', 'updated_at']); - } - - @override - final UserRoleQueryValues values = new UserRoleQueryValues(); - - UserRoleQueryWhere _where; - - @override - get tableName { - return 'user_roles'; - } - - @override - get fields { - return const ['id', 'user_id', 'role_id']; - } - - @override - UserRoleQueryWhere get where { - return _where; - } - - @override - UserRoleQueryWhere newWhereClause() { - return new UserRoleQueryWhere(this); - } - - static UserRole parseRow(List row) { - if (row.every((x) => x == null)) return null; - var model = new UserRole(id: (row[0] as int)); - if (row.length > 3) { - model = model.copyWith(user: UserQuery.parseRow(row.skip(3).toList())); - } - if (row.length > 9) { - model = model.copyWith(role: RoleQuery.parseRow(row.skip(9).toList())); - } - return model; - } - - @override - deserialize(List row) { - return parseRow(row); - } - - @override - insert(executor) { - return executor.transaction(() async { - var result = await super.insert(executor); - return result; - }); - } -} - -class UserRoleQueryWhere extends QueryWhere { - UserRoleQueryWhere(UserRoleQuery query) - : id = new NumericSqlExpressionBuilder(query, 'id'), - userId = new NumericSqlExpressionBuilder(query, 'user_id'), - roleId = new NumericSqlExpressionBuilder(query, 'role_id'); - - final NumericSqlExpressionBuilder id; - - final NumericSqlExpressionBuilder userId; - - final NumericSqlExpressionBuilder roleId; - - @override - get expressionBuilders { - return [id, userId, roleId]; - } -} - -class UserRoleQueryValues extends MapQueryValues { - int get id { - return (values['id'] as int); - } - - set id(int value) => values['id'] = value; - int get userId { - return (values['user_id'] as int); - } - - set userId(int value) => values['user_id'] = value; - int get roleId { - return (values['role_id'] as int); - } - - set roleId(int value) => values['role_id'] = value; - void copyFrom(UserRole model) { - values.addAll({'id': model.id}); - if (model.user != null) { - values['user_id'] = int.parse(model.user.id); - } - if (model.role != null) { - values['role_id'] = int.parse(model.role.id); - } - } -} - // ************************************************************************** // JsonModelGenerator // ************************************************************************** @@ -488,10 +418,10 @@ class User extends _User { this.username, this.password, this.email, - List<_UserRole> userRoles, + List<_Role> roles, this.createdAt, this.updatedAt}) - : this.userRoles = new List.unmodifiable(userRoles ?? []); + : this.roles = new List.unmodifiable(roles ?? []); @override final String id; @@ -506,7 +436,7 @@ class User extends _User { final String email; @override - final List<_UserRole> userRoles; + final List<_Role> roles; @override final DateTime createdAt; @@ -519,7 +449,7 @@ class User extends _User { String username, String password, String email, - List<_UserRole> userRoles, + List<_Role> roles, DateTime createdAt, DateTime updatedAt}) { return new User( @@ -527,7 +457,7 @@ class User extends _User { username: username ?? this.username, password: password ?? this.password, email: email ?? this.email, - userRoles: userRoles ?? this.userRoles, + roles: roles ?? this.roles, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt); } @@ -538,8 +468,8 @@ class User extends _User { other.username == username && other.password == password && other.email == email && - const ListEquality<_UserRole>(const DefaultEquality<_UserRole>()) - .equals(other.userRoles, userRoles) && + const ListEquality<_Role>(const DefaultEquality<_Role>()) + .equals(other.roles, roles) && other.createdAt == createdAt && other.updatedAt == updatedAt; } @@ -547,7 +477,7 @@ class User extends _User { @override int get hashCode { return hashObjects( - [id, username, password, email, userRoles, createdAt, updatedAt]); + [id, username, password, email, roles, createdAt, updatedAt]); } Map toJson() { @@ -555,15 +485,62 @@ class User extends _User { } } +@generatedSerializable +class RoleUser extends _RoleUser { + RoleUser({this.id, this.role, this.user, this.createdAt, this.updatedAt}); + + @override + final String id; + + @override + final _Role role; + + @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); + } + + bool operator ==(other) { + return other is _RoleUser && + other.id == id && + other.role == role && + other.user == user && + other.createdAt == createdAt && + other.updatedAt == updatedAt; + } + + @override + int get hashCode { + return hashObjects([id, role, user, createdAt, updatedAt]); + } + + Map toJson() { + return RoleUserSerializer.toMap(this); + } +} + @generatedSerializable class Role extends _Role { - Role( - {this.id, - this.name, - List<_UserRole> userRoles, - this.createdAt, - this.updatedAt}) - : this.userRoles = new List.unmodifiable(userRoles ?? []); + Role({this.id, this.name, List<_User> users, this.createdAt, this.updatedAt}) + : this.users = new List.unmodifiable(users ?? []); @override final String id; @@ -572,7 +549,7 @@ class Role extends _Role { final String name; @override - final List<_UserRole> userRoles; + final List<_User> users; @override final DateTime createdAt; @@ -583,13 +560,13 @@ class Role extends _Role { Role copyWith( {String id, String name, - List<_UserRole> userRoles, + List<_User> users, DateTime createdAt, DateTime updatedAt}) { return new Role( id: id ?? this.id, name: name ?? this.name, - userRoles: userRoles ?? this.userRoles, + users: users ?? this.users, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt); } @@ -598,15 +575,15 @@ class Role extends _Role { return other is _Role && other.id == id && other.name == name && - const ListEquality<_UserRole>(const DefaultEquality<_UserRole>()) - .equals(other.userRoles, userRoles) && + const ListEquality<_User>(const DefaultEquality<_User>()) + .equals(other.users, users) && other.createdAt == createdAt && other.updatedAt == updatedAt; } @override int get hashCode { - return hashObjects([id, name, userRoles, createdAt, updatedAt]); + return hashObjects([id, name, users, createdAt, updatedAt]); } Map toJson() { @@ -614,41 +591,6 @@ class Role extends _Role { } } -@generatedSerializable -class UserRole implements _UserRole { - const UserRole({this.id, this.user, this.role}); - - @override - final int id; - - @override - final _User user; - - @override - final _Role role; - - UserRole copyWith({int id, _User user, _Role role}) { - return new UserRole( - id: id ?? this.id, user: user ?? this.user, role: role ?? this.role); - } - - bool operator ==(other) { - return other is _UserRole && - other.id == id && - other.user == user && - other.role == role; - } - - @override - int get hashCode { - return hashObjects([id, user, role]); - } - - Map toJson() { - return UserRoleSerializer.toMap(this); - } -} - // ************************************************************************** // SerializerGenerator // ************************************************************************** @@ -660,10 +602,10 @@ abstract class UserSerializer { username: map['username'] as String, password: map['password'] as String, email: map['email'] as String, - userRoles: map['user_roles'] is Iterable - ? new List.unmodifiable(((map['user_roles'] as Iterable) + roles: map['roles'] is Iterable + ? new List.unmodifiable(((map['roles'] as Iterable) .where((x) => x is Map) as Iterable) - .map(UserRoleSerializer.fromMap)) + .map(RoleSerializer.fromMap)) : null, createdAt: map['created_at'] != null ? (map['created_at'] is DateTime @@ -686,8 +628,7 @@ abstract class UserSerializer { 'username': model.username, 'password': model.password, 'email': model.email, - 'user_roles': - model.userRoles?.map((m) => UserRoleSerializer.toMap(m))?.toList(), + 'roles': model.roles?.map((m) => RoleSerializer.toMap(m))?.toList(), 'created_at': model.createdAt?.toIso8601String(), 'updated_at': model.updatedAt?.toIso8601String() }; @@ -700,7 +641,7 @@ abstract class UserFields { username, password, email, - userRoles, + roles, createdAt, updatedAt ]; @@ -713,7 +654,63 @@ abstract class UserFields { static const String email = 'email'; - static const String userRoles = 'user_roles'; + static const String roles = 'roles'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; +} + +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); + } + + static Map toMap(_RoleUser model) { + if (model == null) { + 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() + }; + } +} + +abstract class RoleUserFields { + static const List allFields = const [ + id, + role, + user, + createdAt, + updatedAt + ]; + + static const String id = 'id'; + + static const String role = 'role'; + + static const String user = 'user'; static const String createdAt = 'created_at'; @@ -725,10 +722,10 @@ abstract class RoleSerializer { return new Role( id: map['id'] as String, name: map['name'] as String, - userRoles: map['user_roles'] is Iterable - ? new List.unmodifiable(((map['user_roles'] as Iterable) + users: map['users'] is Iterable + ? new List.unmodifiable(((map['users'] as Iterable) .where((x) => x is Map) as Iterable) - .map(UserRoleSerializer.fromMap)) + .map(UserSerializer.fromMap)) : null, createdAt: map['created_at'] != null ? (map['created_at'] is DateTime @@ -749,8 +746,7 @@ abstract class RoleSerializer { return { 'id': model.id, 'name': model.name, - 'user_roles': - model.userRoles?.map((m) => UserRoleSerializer.toMap(m))?.toList(), + 'users': model.users?.map((m) => UserSerializer.toMap(m))?.toList(), 'created_at': model.createdAt?.toIso8601String(), 'updated_at': model.updatedAt?.toIso8601String() }; @@ -761,7 +757,7 @@ abstract class RoleFields { static const List allFields = const [ id, name, - userRoles, + users, createdAt, updatedAt ]; @@ -770,43 +766,9 @@ abstract class RoleFields { static const String name = 'name'; - static const String userRoles = 'user_roles'; + static const String users = 'users'; static const String createdAt = 'created_at'; static const String updatedAt = 'updated_at'; } - -abstract class UserRoleSerializer { - static UserRole fromMap(Map map) { - return new UserRole( - id: map['id'] as int, - user: map['user'] != null - ? UserSerializer.fromMap(map['user'] as Map) - : null, - role: map['role'] != null - ? RoleSerializer.fromMap(map['role'] as Map) - : null); - } - - static Map toMap(_UserRole model) { - if (model == null) { - return null; - } - return { - 'id': model.id, - 'user': UserSerializer.toMap(model.user), - 'role': RoleSerializer.toMap(model.role) - }; - } -} - -abstract class UserRoleFields { - static const List allFields = const [id, user, role]; - - static const String id = 'id'; - - static const String user = 'user'; - - static const String role = 'role'; -}