Joins in the works

This commit is contained in:
Tobe O 2017-12-07 03:21:49 -05:00
parent d097f6bb35
commit ba0fb80448
30 changed files with 1346 additions and 10 deletions

View file

@ -1,6 +1,12 @@
const ORM orm = const ORM();
class ORM { class ORM {
final String tableName; final String tableName;
const ORM([this.tableName]); const ORM([this.tableName]);
} }
const ORM orm = const ORM(); class CanJoin {
final Type type;
final String foreignKey;
const CanJoin(this.type, this.foreignKey);
}

View file

@ -21,7 +21,8 @@ String sanitizeExpression(String unsafe) {
continue; continue;
// Otherwise, add the next char, unless it's a null byte. // Otherwise, add the next char, unless it's a null byte.
else if ((ch = scanner.readChar()) != 0 && ch != null) buf.writeCharCode(ch); else if ((ch = scanner.readChar()) != 0 && ch != null)
buf.writeCharCode(ch);
} }
return buf.toString(); return buf.toString();

View file

@ -1,5 +1,5 @@
name: angel_orm name: angel_orm
version: 1.0.0-alpha+10 version: 1.0.0-alpha+12
description: Runtime support for Angel's ORM. description: Runtime support for Angel's ORM.
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/orm homepage: https://github.com/angel-dart/orm

View file

@ -99,7 +99,7 @@ Future<PostgresBuildContext> buildContext(
var raw = await serialize.buildContext(clazz, null, buildStep, resolver, var raw = await serialize.buildContext(clazz, null, buildStep, resolver,
autoSnakeCaseNames != false, autoIdAndDateFields != false); autoSnakeCaseNames != false, autoIdAndDateFields != false);
var ctx = await PostgresBuildContext.create( var ctx = await PostgresBuildContext.create(
raw, annotation, resolver, buildStep, clazz, raw, annotation, resolver, buildStep,
tableName: (annotation.tableName?.isNotEmpty == true) tableName: (annotation.tableName?.isNotEmpty == true)
? annotation.tableName ? annotation.tableName
: pluralize(new ReCase(clazz.name).snakeCase), : pluralize(new ReCase(clazz.name).snakeCase),
@ -110,6 +110,19 @@ Future<PostgresBuildContext> buildContext(
for (var field in raw.fields) { for (var field in raw.fields) {
fieldNames.add(field.name); fieldNames.add(field.name);
// Check for joins.
var canJoins = canJoinTypeChecker.annotationsOf(field);
for (var ann in canJoins) {
var cr = new ConstantReader(ann);
ctx.joins[field.name] ??= [];
ctx.joins[field.name].add(new JoinContext(
resolveModelAncestor(cr.read('type').typeValue),
cr.read('foreignKey').stringValue,
));
}
// Check for relationship. If so, skip. // Check for relationship. If so, skip.
var relationshipAnnotation = var relationshipAnnotation =
relationshipTypeChecker.firstAnnotationOf(field); relationshipTypeChecker.firstAnnotationOf(field);

View file

@ -6,6 +6,7 @@ import 'package:build/build.dart';
import 'package:code_builder/dart/async.dart'; import 'package:code_builder/dart/async.dart';
import 'package:code_builder/dart/core.dart'; import 'package:code_builder/dart/core.dart';
import 'package:code_builder/code_builder.dart'; import 'package:code_builder/code_builder.dart';
import 'package:inflection/inflection.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:recase/recase.dart'; import 'package:recase/recase.dart';
import 'package:source_gen/source_gen.dart' hide LibraryBuilder; import 'package:source_gen/source_gen.dart' hide LibraryBuilder;
@ -100,8 +101,12 @@ class PostgresOrmGenerator extends GeneratorForAnnotation<ORM> {
for (var element in contexts.keys) { for (var element in contexts.keys) {
if (!done.contains(element.name)) { if (!done.contains(element.name)) {
var ctx = contexts[element]; var ctx = contexts[element];
lib.addMember(await buildQueryClass(ctx)); var queryClass = await buildQueryClass(ctx);
if (ctx.joins.isNotEmpty)
await buildJoins(queryClass, lib, ctx, resolver, buildStep);
lib.addMember(queryClass);
lib.addMember(buildWhereClass(ctx)); lib.addMember(buildWhereClass(ctx));
lib.addMember(buildFieldClass(ctx));
done.add(element.name); done.add(element.name);
} }
} }
@ -252,8 +257,10 @@ class PostgresOrmGenerator extends GeneratorForAnnotation<ORM> {
// Write prefix, or default to SELECT // Write prefix, or default to SELECT
var prefix = reference('prefix'); var prefix = reference('prefix');
meth.addStatement(buf.invoke('write', [ meth.addStatement(buf.invoke('write', [
prefix.notEquals(literal(null)).ternary(prefix, prefix.notEquals(literal(null)).ternary(
literal('SELECT ${await computeSelector(ctx)} FROM "${ctx.tableName}"')) prefix,
literal(
'SELECT ${await computeSelector(ctx)} FROM "${ctx.tableName}"'))
])); ]));
var relationsIfThen = ifThen(prefix.equals(literal(null))); var relationsIfThen = ifThen(prefix.equals(literal(null)));
@ -966,4 +973,70 @@ class PostgresOrmGenerator extends GeneratorForAnnotation<ORM> {
return clazz; return clazz;
} }
ClassBuilder buildFieldClass(PostgresBuildContext ctx) {
var clazz = new ClassBuilder(ctx.reCase.pascalCase + 'Fields');
for (var field in ctx.fields) {
clazz.addField(
varConst(field.name,
value: literal(ctx.resolveFieldName(field.name))),
asStatic: true);
}
return clazz;
}
Future buildJoins(ClassBuilder queryClass, LibraryBuilder lib,
PostgresBuildContext ctx, Resolver resolver, BuildStep buildStep) async {
Map<int, PostgresBuildContext> contexts = {};
Map<PostgresBuildContext, Map<String, JoinContext>> targets = {};
for (var fieldName in ctx.joins.keys) {
print('${ctx.originalClassName}:$fieldName');
var joins = ctx.joins[fieldName];
for (var join in joins) {
PostgresBuildContext refType;
if (contexts.containsKey(join.type.hashCode))
refType = contexts[join.type.hashCode];
else {
var clazz = join.type.element as ClassElement;
var annotation = ormTypeChecker.firstAnnotationOf(join.type.element);
var ctx = await buildContext(
clazz,
reviveOrm(new ConstantReader(annotation)),
buildStep,
resolver,
autoSnakeCaseNames,
autoIdAndDateFields,
);
contexts[join.type.hashCode] = refType = ctx;
}
var targetMap = targets.putIfAbsent(refType, () => {});
var localFieldName = ctx.resolveFieldName(fieldName);
targetMap.putIfAbsent(localFieldName, () => join);
}
}
if (targets.isNotEmpty) {
/*
var result = await OrderQuery.joinCustomers(
connection,
OrderQueryFields.customerId
);
*/
for (var refType in targets.keys) {
var refTypeRc = new ReCase(pluralize(refType.modelClassName));
var refTypePlural = refTypeRc.pascalCase;
var joinMethod = new MethodBuilder('join$refTypePlural');
queryClass.addMethod(joinMethod, asStatic: true);
joinMethod.addPositional(
parameter('connection', [ctx.postgreSQLConnectionBuilder]));
}
}
}
} }

View file

@ -3,6 +3,7 @@ import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/generated/resolver.dart'; import 'package:analyzer/src/generated/resolver.dart';
import 'package:angel_model/angel_model.dart';
import 'package:angel_orm/angel_orm.dart'; import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize_generator/context.dart'; import 'package:angel_serialize_generator/context.dart';
import 'package:build/build.dart'; import 'package:build/build.dart';
@ -12,6 +13,33 @@ import 'package:recase/recase.dart';
import 'package:source_gen/source_gen.dart'; import 'package:source_gen/source_gen.dart';
import 'build_context.dart'; import 'build_context.dart';
const TypeChecker canJoinTypeChecker = const TypeChecker.fromRuntime(CanJoin);
DartType resolveModelAncestor(DartType type) {
DartType refType = type;
while (refType != null) {
if (!const TypeChecker.fromRuntime(Model).isAssignableFromType(refType)) {
var parent = (refType.element as ClassElement).allSupertypes[0];
if (parent != refType)
refType = parent;
else
refType = null;
} else
break;
}
if (refType != null) return refType;
throw '${type.name} does not extend Model.';
}
class JoinContext {
final DartType type;
final String foreignKey;
JoinContext(this.type, this.foreignKey);
}
class PostgresBuildContext extends BuildContext { class PostgresBuildContext extends BuildContext {
LibraryElement _libraryCache; LibraryElement _libraryCache;
TypeProvider _typeProviderCache; TypeProvider _typeProviderCache;
@ -23,28 +51,36 @@ class PostgresBuildContext extends BuildContext {
final Map<String, Relationship> _populatedRelationships = {}; final Map<String, Relationship> _populatedRelationships = {};
final Map<String, Column> columnInfo = {}; final Map<String, Column> columnInfo = {};
final Map<String, IndexType> indices = {}; final Map<String, IndexType> indices = {};
final Map<String, List<JoinContext>> joins = {};
final Map<String, Relationship> relationships = {}; final Map<String, Relationship> relationships = {};
final bool autoSnakeCaseNames, autoIdAndDateFields; final bool autoSnakeCaseNames, autoIdAndDateFields;
final String tableName; final String tableName;
final ORM ormAnnotation; final ORM ormAnnotation;
final ClassElement element;
final BuildContext raw; final BuildContext raw;
final Resolver resolver; final Resolver resolver;
final BuildStep buildStep; final BuildStep buildStep;
ReCase _reCase;
String primaryKeyName = 'id'; String primaryKeyName = 'id';
PostgresBuildContext._( PostgresBuildContext._(
this.raw, this.ormAnnotation, this.resolver, this.buildStep, this.element, this.raw, this.ormAnnotation, this.resolver, this.buildStep,
{this.tableName, this.autoSnakeCaseNames, this.autoIdAndDateFields}) {this.tableName, this.autoSnakeCaseNames, this.autoIdAndDateFields})
: super(raw.annotation, : super(raw.annotation,
originalClassName: raw.originalClassName, originalClassName: raw.originalClassName,
sourceFilename: raw.sourceFilename); sourceFilename: raw.sourceFilename);
static Future<PostgresBuildContext> create(BuildContext raw, static Future<PostgresBuildContext> create(
ORM ormAnnotation, Resolver resolver, BuildStep buildStep, ClassElement element,
BuildContext raw,
ORM ormAnnotation,
Resolver resolver,
BuildStep buildStep,
{String tableName, {String tableName,
bool autoSnakeCaseNames, bool autoSnakeCaseNames,
bool autoIdAndDateFields}) async { bool autoIdAndDateFields}) async {
var ctx = new PostgresBuildContext._( var ctx = new PostgresBuildContext._(
element,
raw, raw,
ormAnnotation, ormAnnotation,
resolver, resolver,
@ -62,6 +98,8 @@ class PostgresBuildContext extends BuildContext {
final List<FieldElement> fields = [], relationshipFields = []; final List<FieldElement> fields = [], relationshipFields = [];
ReCase get reCase => _reCase ?? new ReCase(modelClassName);
TypeBuilder get modelClassBuilder => TypeBuilder get modelClassBuilder =>
_modelClassBuilder ??= new TypeBuilder(modelClassName); _modelClassBuilder ??= new TypeBuilder(modelClassName);
@ -179,6 +217,11 @@ class PostgresBuildContext extends BuildContext {
'Invalid relationship type: ${relationship.type}'); 'Invalid relationship type: ${relationship.type}');
}); });
} }
@override
String toString() {
return 'PostgresBuildContext: $originalClassName';
}
} }
class PopulatedRelationship extends Relationship { class PopulatedRelationship extends Relationship {

View file

@ -241,3 +241,13 @@ class AuthorQueryWhere {
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND ')); : ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
} }
} }
class AuthorFields {
static const id = 'id';
static const name = 'name';
static const createdAt = 'created_at';
static const updatedAt = 'updated_at';
}

View file

@ -263,3 +263,15 @@ class BookQueryWhere {
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND ')); : ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
} }
} }
class BookFields {
static const id = 'id';
static const name = 'name';
static const createdAt = 'created_at';
static const updatedAt = 'updated_at';
static const authorId = 'author_id';
}

View file

@ -281,3 +281,19 @@ class CarQueryWhere {
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND ')); : ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
} }
} }
class CarFields {
static const id = 'id';
static const make = 'make';
static const description = 'description';
static const familyFriendly = 'family_friendly';
static const recalledAt = 'recalled_at';
static const createdAt = 'created_at';
static const updatedAt = 'updated_at';
}

View file

@ -0,0 +1,11 @@
library angel_orm_generator.test.models.customer;
import 'package:angel_model/angel_model.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize/angel_serialize.dart';
part 'customer.g.dart';
@orm
@serializable
class _Customer extends Model {
}

View file

@ -0,0 +1 @@
DROP TABLE "customers";

View file

@ -0,0 +1,47 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of angel_orm_generator.test.models.customer;
// **************************************************************************
// Generator: JsonModelGenerator
// **************************************************************************
class Customer extends _Customer {
@override
String id;
@override
DateTime createdAt;
@override
DateTime updatedAt;
Customer({this.id, this.createdAt, this.updatedAt});
factory Customer.fromJson(Map data) {
return new Customer(
id: data['id'],
createdAt: data['created_at'] is DateTime
? data['created_at']
: (data['created_at'] is String
? DateTime.parse(data['created_at'])
: null),
updatedAt: data['updated_at'] is DateTime
? data['updated_at']
: (data['updated_at'] is String
? DateTime.parse(data['updated_at'])
: null));
}
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt == null ? null : createdAt.toIso8601String(),
'updated_at': updatedAt == null ? null : updatedAt.toIso8601String()
};
static Customer parse(Map map) => new Customer.fromJson(map);
Customer clone() {
return new Customer.fromJson(toJson());
}
}

View file

@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// Generator: MigrationGenerator
// **************************************************************************
import 'package:angel_migration/angel_migration.dart';
class CustomerMigration extends Migration {
@override
up(Schema schema) {
schema.create('customers', (table) {
table.serial('id')..primaryKey();
table.timeStamp('created_at');
table.timeStamp('updated_at');
});
}
@override
down(Schema schema) {
schema.drop('customers');
}
}

View file

@ -0,0 +1,234 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// Generator: PostgresOrmGenerator
// **************************************************************************
import 'dart:async';
import 'package:angel_orm/angel_orm.dart';
import 'package:postgres/postgres.dart';
import 'customer.dart';
class CustomerQuery {
final Map<CustomerQuery, bool> _unions = {};
String _sortKey;
String _sortMode;
int limit;
int offset;
final List<CustomerQueryWhere> _or = [];
final CustomerQueryWhere where = new CustomerQueryWhere();
void union(CustomerQuery query) {
_unions[query] = false;
}
void unionAll(CustomerQuery query) {
_unions[query] = true;
}
void sortDescending(String key) {
_sortMode = 'Descending';
_sortKey = ('' + key);
}
void sortAscending(String key) {
_sortMode = 'Ascending';
_sortKey = ('' + key);
}
void or(CustomerQueryWhere selector) {
_or.add(selector);
}
String toSql([String prefix]) {
var buf = new StringBuffer();
buf.write(prefix != null
? prefix
: 'SELECT id, created_at, updated_at FROM "customers"');
if (prefix == null) {}
var whereClause = where.toWhereClause();
if (whereClause != null) {
buf.write(' ' + whereClause);
}
_or.forEach((x) {
var whereClause = x.toWhereClause(keyword: false);
if (whereClause != null) {
buf.write(' OR (' + whereClause + ')');
}
});
if (prefix == null) {
if (limit != null) {
buf.write(' LIMIT ' + limit.toString());
}
if (offset != null) {
buf.write(' OFFSET ' + offset.toString());
}
if (_sortMode == 'Descending') {
buf.write(' ORDER BY "' + _sortKey + '" DESC');
}
if (_sortMode == 'Ascending') {
buf.write(' ORDER BY "' + _sortKey + '" ASC');
}
_unions.forEach((query, all) {
buf.write(' UNION');
if (all) {
buf.write(' ALL');
}
buf.write(' (');
var sql = query.toSql().replaceAll(';', '');
buf.write(sql + ')');
});
buf.write(';');
}
return buf.toString();
}
static Customer parseRow(List row) {
var result = new Customer.fromJson(
{'id': row[0].toString(), 'created_at': row[1], 'updated_at': row[2]});
return result;
}
Stream<Customer> get(PostgreSQLConnection connection) {
StreamController<Customer> ctrl = new StreamController<Customer>();
connection.query(toSql()).then((rows) async {
var futures = rows.map((row) async {
var parsed = parseRow(row);
return parsed;
});
var output = await Future.wait(futures);
output.forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
static Future<Customer> getOne(int id, PostgreSQLConnection connection) {
var query = new CustomerQuery();
query.where.id.equals(id);
return query.get(connection).first.catchError((_) => null);
}
Stream<Customer> update(PostgreSQLConnection connection,
{DateTime createdAt, DateTime updatedAt}) {
var buf = new StringBuffer(
'UPDATE "customers" SET ("created_at", "updated_at") = (@createdAt, @updatedAt) ');
var whereClause = where.toWhereClause();
if (whereClause != null) {
buf.write(whereClause);
}
var __ormNow__ = new DateTime.now();
var ctrl = new StreamController<Customer>();
connection.query(
buf.toString() + ' RETURNING "id", "created_at", "updated_at";',
substitutionValues: {
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
}).then((rows) async {
var futures = rows.map((row) async {
var parsed = parseRow(row);
return parsed;
});
var output = await Future.wait(futures);
output.forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
Stream<Customer> delete(PostgreSQLConnection connection) {
StreamController<Customer> ctrl = new StreamController<Customer>();
connection
.query(toSql('DELETE FROM "customers"') +
' RETURNING "id", "created_at", "updated_at";')
.then((rows) async {
var futures = rows.map((row) async {
var parsed = parseRow(row);
return parsed;
});
var output = await Future.wait(futures);
output.forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
static Future<Customer> deleteOne(int id, PostgreSQLConnection connection) {
var query = new CustomerQuery();
query.where.id.equals(id);
return query.delete(connection).first;
}
static Future<Customer> insert(PostgreSQLConnection connection,
{DateTime createdAt, DateTime updatedAt}) async {
var __ormNow__ = new DateTime.now();
var result = await connection.query(
'INSERT INTO "customers" ("created_at", "updated_at") VALUES (@createdAt, @updatedAt) RETURNING "id", "created_at", "updated_at";',
substitutionValues: {
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
});
var output = parseRow(result[0]);
return output;
}
static Future<Customer> insertCustomer(
PostgreSQLConnection connection, Customer customer) {
return CustomerQuery.insert(connection,
createdAt: customer.createdAt, updatedAt: customer.updatedAt);
}
static Future<Customer> updateCustomer(
PostgreSQLConnection connection, Customer customer) {
var query = new CustomerQuery();
query.where.id.equals(int.parse(customer.id));
return query
.update(connection,
createdAt: customer.createdAt, updatedAt: customer.updatedAt)
.first;
}
static Stream<Customer> getAll(PostgreSQLConnection connection) =>
new CustomerQuery().get(connection);
}
class CustomerQueryWhere {
final NumericSqlExpressionBuilder<int> id =
new NumericSqlExpressionBuilder<int>();
final DateTimeSqlExpressionBuilder createdAt =
new DateTimeSqlExpressionBuilder('customers.created_at');
final DateTimeSqlExpressionBuilder updatedAt =
new DateTimeSqlExpressionBuilder('customers.updated_at');
String toWhereClause({bool keyword}) {
final List<String> expressions = [];
if (id.hasValue) {
expressions.add('customers.id ' + id.compile());
}
if (createdAt.hasValue) {
expressions.add(createdAt.compile());
}
if (updatedAt.hasValue) {
expressions.add(updatedAt.compile());
}
return expressions.isEmpty
? null
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
}
}
class CustomerFields {
static const id = 'id';
static const createdAt = 'created_at';
static const updatedAt = 'updated_at';
}

View file

@ -0,0 +1,143 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// Generator: PostgresServiceGenerator
// **************************************************************************
import 'dart:async';
import 'package:angel_framework/angel_framework.dart';
import 'package:postgres/postgres.dart';
import 'customer.dart';
import 'customer.orm.g.dart';
class CustomerService extends Service {
final PostgreSQLConnection connection;
final bool allowRemoveAll;
final bool allowQuery;
CustomerService(this.connection,
{this.allowRemoveAll: false, this.allowQuery: false});
CustomerQuery buildQuery(Map params) {
var query = new CustomerQuery();
if (params['query'] is Map) {
query.where.id.equals(params['query']['id']);
query.where.createdAt.equals(params['query']['created_at'] is String
? DateTime.parse(params['query']['created_at'])
: params['query']['created_at'] != null
? params['query']['created_at'] is String
? DateTime.parse(params['query']['created_at'])
: params['query']['created_at']
: new DateTime.now());
query.where.updatedAt.equals(params['query']['updated_at'] is String
? DateTime.parse(params['query']['updated_at'])
: params['query']['updated_at'] != null
? params['query']['updated_at'] is String
? DateTime.parse(params['query']['updated_at'])
: params['query']['updated_at']
: new DateTime.now());
}
return query;
}
int toId(id) {
if (id is int) {
return id;
} else {
if (id == 'null' || id == null) {
return null;
} else {
return int.parse(id.toString());
}
}
}
Customer applyData(data) {
if (data is Customer || data == null) {
return data;
}
if (data is Map) {
var query = new Customer();
if (data.containsKey('created_at')) {
query.createdAt = data['created_at'] is String
? DateTime.parse(data['created_at'])
: data['created_at'] != null
? data['created_at'] is String
? DateTime.parse(data['created_at'])
: data['created_at']
: new DateTime.now();
}
if (data.containsKey('updated_at')) {
query.updatedAt = data['updated_at'] is String
? DateTime.parse(data['updated_at'])
: data['updated_at'] != null
? data['updated_at'] is String
? DateTime.parse(data['updated_at'])
: data['updated_at']
: new DateTime.now();
}
return query;
} else
throw new AngelHttpException.badRequest(message: 'Invalid data.');
}
Future<List<Customer>> index([Map params]) {
return buildQuery(params).get(connection).toList();
}
Future<Customer> create(data, [Map params]) {
return CustomerQuery.insertCustomer(connection, applyData(data));
}
Future<Customer> read(id, [Map params]) {
var query = buildQuery(params);
query.where.id.equals(toId(id));
return query.get(connection).first.catchError((_) {
new AngelHttpException.notFound(
message: 'No record found for ID ' + id.toString());
});
}
Future<Customer> remove(id, [Map params]) {
var query = buildQuery(params);
query.where.id.equals(toId(id));
return query.delete(connection).first.catchError((_) {
new AngelHttpException.notFound(
message: 'No record found for ID ' + id.toString());
});
}
Future<Customer> update(id, data, [Map params]) {
return CustomerQuery.updateCustomer(connection, applyData(data));
}
Future<Customer> modify(id, data, [Map params]) async {
var query = await read(toId(id), params);
if (data is Customer) {
query = data;
}
if (data is Map) {
if (data.containsKey('created_at')) {
query.createdAt = data['created_at'] is String
? DateTime.parse(data['created_at'])
: data['created_at'] != null
? data['created_at'] is String
? DateTime.parse(data['created_at'])
: data['created_at']
: new DateTime.now();
}
if (data.containsKey('updated_at')) {
query.updatedAt = data['updated_at'] is String
? DateTime.parse(data['updated_at'])
: data['updated_at'] != null
? data['updated_at'] is String
? DateTime.parse(data['updated_at'])
: data['updated_at']
: new DateTime.now();
}
}
return await CustomerQuery.updateCustomer(connection, query);
}
}

View file

@ -0,0 +1,6 @@
CREATE TEMPORARY TABLE "customers" (
"id" serial,
"created_at" timestamp,
"updated_at" timestamp,
PRIMARY KEY(id)
);

View file

@ -250,3 +250,15 @@ class FootQueryWhere {
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND ')); : ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
} }
} }
class FootFields {
static const id = 'id';
static const legId = 'leg_id';
static const nToes = 'n_toes';
static const createdAt = 'created_at';
static const updatedAt = 'updated_at';
}

View file

@ -255,3 +255,15 @@ class FruitQueryWhere {
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND ')); : ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
} }
} }
class FruitFields {
static const id = 'id';
static const treeId = 'tree_id';
static const commonName = 'common_name';
static const createdAt = 'created_at';
static const updatedAt = 'updated_at';
}

View file

@ -255,3 +255,13 @@ class LegQueryWhere {
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND ')); : ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
} }
} }
class LegFields {
static const id = 'id';
static const name = 'name';
static const createdAt = 'created_at';
static const updatedAt = 'updated_at';
}

View file

@ -0,0 +1,17 @@
library angel_orm_generator.test.models.order;
import 'package:angel_model/angel_model.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize/angel_serialize.dart';
import 'customer.dart';
part 'order.g.dart';
@orm
@serializable
class _Order extends Model {
@CanJoin(Customer, 'id')
int customerId;
int employeeId;
DateTime orderDate;
int shipperId;
}

View file

@ -0,0 +1 @@
DROP TABLE "orders";

View file

@ -0,0 +1,78 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of angel_orm_generator.test.models.order;
// **************************************************************************
// Generator: JsonModelGenerator
// **************************************************************************
class Order extends _Order {
@override
String id;
@override
int customerId;
@override
int employeeId;
@override
DateTime orderDate;
@override
int shipperId;
@override
DateTime createdAt;
@override
DateTime updatedAt;
Order(
{this.id,
this.customerId,
this.employeeId,
this.orderDate,
this.shipperId,
this.createdAt,
this.updatedAt});
factory Order.fromJson(Map data) {
return new Order(
id: data['id'],
customerId: data['customer_id'],
employeeId: data['employee_id'],
orderDate: data['order_date'] is DateTime
? data['order_date']
: (data['order_date'] is String
? DateTime.parse(data['order_date'])
: null),
shipperId: data['shipper_id'],
createdAt: data['created_at'] is DateTime
? data['created_at']
: (data['created_at'] is String
? DateTime.parse(data['created_at'])
: null),
updatedAt: data['updated_at'] is DateTime
? data['updated_at']
: (data['updated_at'] is String
? DateTime.parse(data['updated_at'])
: null));
}
Map<String, dynamic> toJson() => {
'id': id,
'customer_id': customerId,
'employee_id': employeeId,
'order_date': orderDate == null ? null : orderDate.toIso8601String(),
'shipper_id': shipperId,
'created_at': createdAt == null ? null : createdAt.toIso8601String(),
'updated_at': updatedAt == null ? null : updatedAt.toIso8601String()
};
static Order parse(Map map) => new Order.fromJson(map);
Order clone() {
return new Order.fromJson(toJson());
}
}

View file

@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// Generator: MigrationGenerator
// **************************************************************************
import 'package:angel_migration/angel_migration.dart';
class OrderMigration extends Migration {
@override
up(Schema schema) {
schema.create('orders', (table) {
table.serial('id')..primaryKey();
table.integer('customer_id');
table.integer('employee_id');
table.timeStamp('order_date');
table.integer('shipper_id');
table.timeStamp('created_at');
table.timeStamp('updated_at');
});
}
@override
down(Schema schema) {
schema.drop('orders');
}
}

View file

@ -0,0 +1,305 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// Generator: PostgresOrmGenerator
// **************************************************************************
import 'dart:async';
import 'package:angel_orm/angel_orm.dart';
import 'package:postgres/postgres.dart';
import 'order.dart';
class OrderQuery {
final Map<OrderQuery, bool> _unions = {};
String _sortKey;
String _sortMode;
int limit;
int offset;
final List<OrderQueryWhere> _or = [];
final OrderQueryWhere where = new OrderQueryWhere();
void union(OrderQuery query) {
_unions[query] = false;
}
void unionAll(OrderQuery query) {
_unions[query] = true;
}
void sortDescending(String key) {
_sortMode = 'Descending';
_sortKey = ('' + key);
}
void sortAscending(String key) {
_sortMode = 'Ascending';
_sortKey = ('' + key);
}
void or(OrderQueryWhere selector) {
_or.add(selector);
}
String toSql([String prefix]) {
var buf = new StringBuffer();
buf.write(prefix != null
? prefix
: 'SELECT id, customer_id, employee_id, order_date, shipper_id, created_at, updated_at FROM "orders"');
if (prefix == null) {}
var whereClause = where.toWhereClause();
if (whereClause != null) {
buf.write(' ' + whereClause);
}
_or.forEach((x) {
var whereClause = x.toWhereClause(keyword: false);
if (whereClause != null) {
buf.write(' OR (' + whereClause + ')');
}
});
if (prefix == null) {
if (limit != null) {
buf.write(' LIMIT ' + limit.toString());
}
if (offset != null) {
buf.write(' OFFSET ' + offset.toString());
}
if (_sortMode == 'Descending') {
buf.write(' ORDER BY "' + _sortKey + '" DESC');
}
if (_sortMode == 'Ascending') {
buf.write(' ORDER BY "' + _sortKey + '" ASC');
}
_unions.forEach((query, all) {
buf.write(' UNION');
if (all) {
buf.write(' ALL');
}
buf.write(' (');
var sql = query.toSql().replaceAll(';', '');
buf.write(sql + ')');
});
buf.write(';');
}
return buf.toString();
}
static Order parseRow(List row) {
var result = new Order.fromJson({
'id': row[0].toString(),
'customer_id': row[1],
'employee_id': row[2],
'order_date': row[3],
'shipper_id': row[4],
'created_at': row[5],
'updated_at': row[6]
});
return result;
}
Stream<Order> get(PostgreSQLConnection connection) {
StreamController<Order> ctrl = new StreamController<Order>();
connection.query(toSql()).then((rows) async {
var futures = rows.map((row) async {
var parsed = parseRow(row);
return parsed;
});
var output = await Future.wait(futures);
output.forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
static Future<Order> getOne(int id, PostgreSQLConnection connection) {
var query = new OrderQuery();
query.where.id.equals(id);
return query.get(connection).first.catchError((_) => null);
}
Stream<Order> update(PostgreSQLConnection connection,
{int customerId,
int employeeId,
DateTime orderDate,
int shipperId,
DateTime createdAt,
DateTime updatedAt}) {
var buf = new StringBuffer(
'UPDATE "orders" SET ("customer_id", "employee_id", "order_date", "shipper_id", "created_at", "updated_at") = (@customerId, @employeeId, @orderDate, @shipperId, @createdAt, @updatedAt) ');
var whereClause = where.toWhereClause();
if (whereClause != null) {
buf.write(whereClause);
}
var __ormNow__ = new DateTime.now();
var ctrl = new StreamController<Order>();
connection.query(
buf.toString() +
' RETURNING "id", "customer_id", "employee_id", "order_date", "shipper_id", "created_at", "updated_at";',
substitutionValues: {
'customerId': customerId,
'employeeId': employeeId,
'orderDate': orderDate,
'shipperId': shipperId,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
}).then((rows) async {
var futures = rows.map((row) async {
var parsed = parseRow(row);
return parsed;
});
var output = await Future.wait(futures);
output.forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
Stream<Order> delete(PostgreSQLConnection connection) {
StreamController<Order> ctrl = new StreamController<Order>();
connection
.query(toSql('DELETE FROM "orders"') +
' RETURNING "id", "customer_id", "employee_id", "order_date", "shipper_id", "created_at", "updated_at";')
.then((rows) async {
var futures = rows.map((row) async {
var parsed = parseRow(row);
return parsed;
});
var output = await Future.wait(futures);
output.forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
static Future<Order> deleteOne(int id, PostgreSQLConnection connection) {
var query = new OrderQuery();
query.where.id.equals(id);
return query.delete(connection).first;
}
static Future<Order> insert(PostgreSQLConnection connection,
{int customerId,
int employeeId,
DateTime orderDate,
int shipperId,
DateTime createdAt,
DateTime updatedAt}) async {
var __ormNow__ = new DateTime.now();
var result = await connection.query(
'INSERT INTO "orders" ("customer_id", "employee_id", "order_date", "shipper_id", "created_at", "updated_at") VALUES (@customerId, @employeeId, @orderDate, @shipperId, @createdAt, @updatedAt) RETURNING "id", "customer_id", "employee_id", "order_date", "shipper_id", "created_at", "updated_at";',
substitutionValues: {
'customerId': customerId,
'employeeId': employeeId,
'orderDate': orderDate,
'shipperId': shipperId,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
});
var output = parseRow(result[0]);
return output;
}
static Future<Order> insertOrder(
PostgreSQLConnection connection, Order order) {
return OrderQuery.insert(connection,
customerId: order.customerId,
employeeId: order.employeeId,
orderDate: order.orderDate,
shipperId: order.shipperId,
createdAt: order.createdAt,
updatedAt: order.updatedAt);
}
static Future<Order> updateOrder(
PostgreSQLConnection connection, Order order) {
var query = new OrderQuery();
query.where.id.equals(int.parse(order.id));
return query
.update(connection,
customerId: order.customerId,
employeeId: order.employeeId,
orderDate: order.orderDate,
shipperId: order.shipperId,
createdAt: order.createdAt,
updatedAt: order.updatedAt)
.first;
}
static Stream<Order> getAll(PostgreSQLConnection connection) =>
new OrderQuery().get(connection);
static joinCustomers(PostgreSQLConnection connection) {
}
}
class OrderQueryWhere {
final NumericSqlExpressionBuilder<int> id =
new NumericSqlExpressionBuilder<int>();
final NumericSqlExpressionBuilder<int> customerId =
new NumericSqlExpressionBuilder<int>();
final NumericSqlExpressionBuilder<int> employeeId =
new NumericSqlExpressionBuilder<int>();
final DateTimeSqlExpressionBuilder orderDate =
new DateTimeSqlExpressionBuilder('orders.order_date');
final NumericSqlExpressionBuilder<int> shipperId =
new NumericSqlExpressionBuilder<int>();
final DateTimeSqlExpressionBuilder createdAt =
new DateTimeSqlExpressionBuilder('orders.created_at');
final DateTimeSqlExpressionBuilder updatedAt =
new DateTimeSqlExpressionBuilder('orders.updated_at');
String toWhereClause({bool keyword}) {
final List<String> expressions = [];
if (id.hasValue) {
expressions.add('orders.id ' + id.compile());
}
if (customerId.hasValue) {
expressions.add('orders.customer_id ' + customerId.compile());
}
if (employeeId.hasValue) {
expressions.add('orders.employee_id ' + employeeId.compile());
}
if (orderDate.hasValue) {
expressions.add(orderDate.compile());
}
if (shipperId.hasValue) {
expressions.add('orders.shipper_id ' + shipperId.compile());
}
if (createdAt.hasValue) {
expressions.add(createdAt.compile());
}
if (updatedAt.hasValue) {
expressions.add(updatedAt.compile());
}
return expressions.isEmpty
? null
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
}
}
class OrderFields {
static const id = 'id';
static const customerId = 'customer_id';
static const employeeId = 'employee_id';
static const orderDate = 'order_date';
static const shipperId = 'shipper_id';
static const createdAt = 'created_at';
static const updatedAt = 'updated_at';
}

View file

@ -0,0 +1,189 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// Generator: PostgresServiceGenerator
// **************************************************************************
import 'dart:async';
import 'package:angel_framework/angel_framework.dart';
import 'package:postgres/postgres.dart';
import 'order.dart';
import 'order.orm.g.dart';
class OrderService extends Service {
final PostgreSQLConnection connection;
final bool allowRemoveAll;
final bool allowQuery;
OrderService(this.connection,
{this.allowRemoveAll: false, this.allowQuery: false});
OrderQuery buildQuery(Map params) {
var query = new OrderQuery();
if (params['query'] is Map) {
query.where.id.equals(params['query']['id']);
query.where.customerId.equals(params['query']['customer_id']);
query.where.employeeId.equals(params['query']['employee_id']);
query.where.orderDate.equals(params['query']['order_date'] is String
? DateTime.parse(params['query']['order_date'])
: params['query']['order_date'] != null
? params['query']['order_date'] is String
? DateTime.parse(params['query']['order_date'])
: params['query']['order_date']
: new DateTime.now());
query.where.shipperId.equals(params['query']['shipper_id']);
query.where.createdAt.equals(params['query']['created_at'] is String
? DateTime.parse(params['query']['created_at'])
: params['query']['created_at'] != null
? params['query']['created_at'] is String
? DateTime.parse(params['query']['created_at'])
: params['query']['created_at']
: new DateTime.now());
query.where.updatedAt.equals(params['query']['updated_at'] is String
? DateTime.parse(params['query']['updated_at'])
: params['query']['updated_at'] != null
? params['query']['updated_at'] is String
? DateTime.parse(params['query']['updated_at'])
: params['query']['updated_at']
: new DateTime.now());
}
return query;
}
int toId(id) {
if (id is int) {
return id;
} else {
if (id == 'null' || id == null) {
return null;
} else {
return int.parse(id.toString());
}
}
}
Order applyData(data) {
if (data is Order || data == null) {
return data;
}
if (data is Map) {
var query = new Order();
if (data.containsKey('customer_id')) {
query.customerId = data['customer_id'];
}
if (data.containsKey('employee_id')) {
query.employeeId = data['employee_id'];
}
if (data.containsKey('order_date')) {
query.orderDate = data['order_date'] is String
? DateTime.parse(data['order_date'])
: data['order_date'] != null
? data['order_date'] is String
? DateTime.parse(data['order_date'])
: data['order_date']
: new DateTime.now();
}
if (data.containsKey('shipper_id')) {
query.shipperId = data['shipper_id'];
}
if (data.containsKey('created_at')) {
query.createdAt = data['created_at'] is String
? DateTime.parse(data['created_at'])
: data['created_at'] != null
? data['created_at'] is String
? DateTime.parse(data['created_at'])
: data['created_at']
: new DateTime.now();
}
if (data.containsKey('updated_at')) {
query.updatedAt = data['updated_at'] is String
? DateTime.parse(data['updated_at'])
: data['updated_at'] != null
? data['updated_at'] is String
? DateTime.parse(data['updated_at'])
: data['updated_at']
: new DateTime.now();
}
return query;
} else
throw new AngelHttpException.badRequest(message: 'Invalid data.');
}
Future<List<Order>> index([Map params]) {
return buildQuery(params).get(connection).toList();
}
Future<Order> create(data, [Map params]) {
return OrderQuery.insertOrder(connection, applyData(data));
}
Future<Order> read(id, [Map params]) {
var query = buildQuery(params);
query.where.id.equals(toId(id));
return query.get(connection).first.catchError((_) {
new AngelHttpException.notFound(
message: 'No record found for ID ' + id.toString());
});
}
Future<Order> remove(id, [Map params]) {
var query = buildQuery(params);
query.where.id.equals(toId(id));
return query.delete(connection).first.catchError((_) {
new AngelHttpException.notFound(
message: 'No record found for ID ' + id.toString());
});
}
Future<Order> update(id, data, [Map params]) {
return OrderQuery.updateOrder(connection, applyData(data));
}
Future<Order> modify(id, data, [Map params]) async {
var query = await read(toId(id), params);
if (data is Order) {
query = data;
}
if (data is Map) {
if (data.containsKey('customer_id')) {
query.customerId = data['customer_id'];
}
if (data.containsKey('employee_id')) {
query.employeeId = data['employee_id'];
}
if (data.containsKey('order_date')) {
query.orderDate = data['order_date'] is String
? DateTime.parse(data['order_date'])
: data['order_date'] != null
? data['order_date'] is String
? DateTime.parse(data['order_date'])
: data['order_date']
: new DateTime.now();
}
if (data.containsKey('shipper_id')) {
query.shipperId = data['shipper_id'];
}
if (data.containsKey('created_at')) {
query.createdAt = data['created_at'] is String
? DateTime.parse(data['created_at'])
: data['created_at'] != null
? data['created_at'] is String
? DateTime.parse(data['created_at'])
: data['created_at']
: new DateTime.now();
}
if (data.containsKey('updated_at')) {
query.updatedAt = data['updated_at'] is String
? DateTime.parse(data['updated_at'])
: data['updated_at'] != null
? data['updated_at'] is String
? DateTime.parse(data['updated_at'])
: data['updated_at']
: new DateTime.now();
}
}
return await OrderQuery.updateOrder(connection, query);
}
}

View file

@ -0,0 +1,10 @@
CREATE TEMPORARY TABLE "orders" (
"id" serial,
"customer_id" int,
"employee_id" int,
"order_date" timestamp,
"shipper_id" int,
"created_at" timestamp,
"updated_at" timestamp,
PRIMARY KEY(id)
);

View file

@ -235,3 +235,13 @@ class RoleQueryWhere {
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND ')); : ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
} }
} }
class RoleFields {
static const id = 'id';
static const name = 'name';
static const createdAt = 'created_at';
static const updatedAt = 'updated_at';
}

View file

@ -260,3 +260,13 @@ class TreeQueryWhere {
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND ')); : ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
} }
} }
class TreeFields {
static const id = 'id';
static const rings = 'rings';
static const createdAt = 'created_at';
static const updatedAt = 'updated_at';
}

View file

@ -283,3 +283,17 @@ class UserQueryWhere {
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND ')); : ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
} }
} }
class UserFields {
static const id = 'id';
static const username = 'username';
static const password = 'password';
static const email = 'email';
static const createdAt = 'created_at';
static const updatedAt = 'updated_at';
}

View file

@ -10,10 +10,12 @@ const List<String> standaloneModels = const [
'test/models/car.dart', 'test/models/car.dart',
'test/models/foot.dart', 'test/models/foot.dart',
'test/models/fruit.dart', 'test/models/fruit.dart',
'test/models/order.dart',
'test/models/role.dart' 'test/models/role.dart'
]; ];
const List<String> dependentModels = const [ const List<String> dependentModels = const [
'test/models/book.dart', 'test/models/book.dart',
'test/models/customer.dart',
'test/models/leg.dart', 'test/models/leg.dart',
'test/models/tree.dart', 'test/models/tree.dart',
'test/models/user.dart' 'test/models/user.dart'