diff --git a/.idea/runConfigurations/tests_in_book_test_dart.xml b/.idea/runConfigurations/tests_in_book_test_dart.xml
new file mode 100644
index 00000000..10d71dd0
--- /dev/null
+++ b/.idea/runConfigurations/tests_in_book_test_dart.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/angel_orm/CHANGELOG.md b/angel_orm/CHANGELOG.md
index 58829678..f7f4c220 100644
--- a/angel_orm/CHANGELOG.md
+++ b/angel_orm/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 1.0.0-alpha+6
+* `DateTimeSqlExpressionBuilder` will no longer automatically
+insert quotation marks around names.
+
# 1.0.0-alpha+5
* Corrected a typo that was causing the aforementioned test failures.
`==` becomes `=`.
diff --git a/angel_orm/lib/src/query.dart b/angel_orm/lib/src/query.dart
index 0d461a90..904ee918 100644
--- a/angel_orm/lib/src/query.dart
+++ b/angel_orm/lib/src/query.dart
@@ -260,7 +260,7 @@ class DateTimeSqlExpressionBuilder implements SqlExpressionBuilder {
bool _change(String _op, DateTime dt, bool time) {
var dateString = time ? DATE_YMD_HMS.format(dt) : DATE_YMD.format(dt);
- _raw = '"$columnName" $_op \'$dateString\'';
+ _raw = '$columnName $_op \'$dateString\'';
return true;
}
@@ -291,40 +291,40 @@ class DateTimeSqlExpressionBuilder implements SqlExpressionBuilder {
@override
void isIn(@checked Iterable values) {
- _raw = '"$columnName" IN (' +
- values.map(DATE_YMD_HMS.format).map((s) => "'$s'").join(', ') +
+ _raw = '$columnName IN (' +
+ values.map(DATE_YMD_HMS.format).map((s) => '$s').join(', ') +
')';
}
@override
void isNotIn(@checked Iterable values) {
- _raw = '"$columnName" NOT IN (' +
- values.map(DATE_YMD_HMS.format).map((s) => "'$s'").join(', ') +
+ _raw = '$columnName NOT IN (' +
+ values.map(DATE_YMD_HMS.format).map((s) => '$s').join(', ') +
')';
}
@override
void isBetween(@checked DateTime lower, @checked DateTime upper) {
var l = DATE_YMD_HMS.format(lower), u = DATE_YMD_HMS.format(upper);
- _raw = "\"$columnName\" BETWEEN '$l' and '$u'";
+ _raw = "$columnName BETWEEN '$l' and '$u'";
}
@override
void isNotBetween(@checked DateTime lower, @checked DateTime upper) {
var l = DATE_YMD_HMS.format(lower), u = DATE_YMD_HMS.format(upper);
- _raw = "\"$columnName\" NOT BETWEEN '$l' and '$u'";
+ _raw = "$columnName NOT BETWEEN '$l' and '$u'";
}
@override
String compile() {
if (_raw?.isNotEmpty == true) return _raw;
List parts = [];
- if (year.hasValue) parts.add('YEAR("$columnName") ${year.compile()}');
- if (month.hasValue) parts.add('MONTH("$columnName") ${month.compile()}');
- if (day.hasValue) parts.add('DAY("$columnName") ${day.compile()}');
- if (hour.hasValue) parts.add('HOUR("$columnName") ${hour.compile()}');
- if (minute.hasValue) parts.add('MINUTE("$columnName") ${minute.compile()}');
- if (second.hasValue) parts.add('SECOND("$columnName") ${second.compile()}');
+ if (year.hasValue) parts.add('YEAR($columnName) ${year.compile()}');
+ if (month.hasValue) parts.add('MONTH($columnName) ${month.compile()}');
+ if (day.hasValue) parts.add('DAY($columnName) ${day.compile()}');
+ if (hour.hasValue) parts.add('HOUR($columnName) ${hour.compile()}');
+ if (minute.hasValue) parts.add('MINUTE($columnName) ${minute.compile()}');
+ if (second.hasValue) parts.add('SECOND($columnName) ${second.compile()}');
return parts.isEmpty ? null : parts.join(' AND ');
}
diff --git a/angel_orm/pubspec.yaml b/angel_orm/pubspec.yaml
index ba343391..9cbc0b23 100644
--- a/angel_orm/pubspec.yaml
+++ b/angel_orm/pubspec.yaml
@@ -1,5 +1,5 @@
name: angel_orm
-version: 1.0.0-alpha+5
+version: 1.0.0-alpha+6
description: Runtime support for Angel's ORM.
author: Tobe O
homepage: https://github.com/angel-dart/orm
diff --git a/angel_orm_generator/CHANGELOG.md b/angel_orm_generator/CHANGELOG.md
index ae80dcb7..381d02fa 100644
--- a/angel_orm_generator/CHANGELOG.md
+++ b/angel_orm_generator/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 1.0.0-alpha+2
+* Added support for `belongsTo` relationships. Still missing `hasOne`, `hasMany`, `belongsToMany`.
+
# 1.0.0-alpha+1
* Closed #12. `insertX` and `updateX` now use `rc.camelCase`, instead of `rc.snakeCase`.
* Closed #13. Added `limit` and `offset` properties to `XQuery`.
diff --git a/angel_orm_generator/lib/src/builder/postgres/build_context.dart b/angel_orm_generator/lib/src/builder/postgres/build_context.dart
index 9efe8a12..390c6140 100644
--- a/angel_orm_generator/lib/src/builder/postgres/build_context.dart
+++ b/angel_orm_generator/lib/src/builder/postgres/build_context.dart
@@ -1,4 +1,6 @@
import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/dart/element/element.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize_generator/src/find_annotation.dart';
import 'package:angel_serialize_generator/build_context.dart' as serialize;
@@ -21,7 +23,9 @@ PostgresBuildContext buildContext(
var ctx = new PostgresBuildContext(raw, annotation, resolver, buildStep,
tableName: annotation.tableName?.isNotEmpty == true
? annotation.tableName
- : pluralize(new ReCase(clazz.name).snakeCase));
+ : pluralize(new ReCase(clazz.name).snakeCase),
+ autoSnakeCaseNames: autoSnakeCaseNames != false,
+ autoIdAndDateFields: autoIdAndDateFields != false);
var relations = new TypeChecker.fromRuntime(Relationship);
List fieldNames = [];
List fields = [];
@@ -107,5 +111,28 @@ PostgresBuildContext buildContext(
}
ctx.fields.addAll(fields);
+
+ // Add belongs to fields
+ // TODO: Do this for belongs to many as well
+ ctx.relationships.forEach((name, r) {
+ var relationship = ctx.populateRelationship(name);
+ var rc = new ReCase(relationship.localKey);
+
+ if (relationship.type == RelationshipType.BELONGS_TO) {
+ var field = new RelationshipConstraintField(
+ rc.camelCase, ctx.typeProvider.intType, name);
+ ctx.fields.add(field);
+ ctx.aliases[field.name] = relationship.localKey;
+ }
+ });
+
return ctx;
}
+
+class RelationshipConstraintField extends FieldElementImpl {
+ @override
+ final DartType type;
+ final String originalName;
+ RelationshipConstraintField(String name, this.type, this.originalName)
+ : super(name, -1);
+}
diff --git a/angel_orm_generator/lib/src/builder/postgres/migration.dart b/angel_orm_generator/lib/src/builder/postgres/migration.dart
index b6461361..0bb04055 100644
--- a/angel_orm_generator/lib/src/builder/postgres/migration.dart
+++ b/angel_orm_generator/lib/src/builder/postgres/migration.dart
@@ -2,6 +2,8 @@ import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:build/build.dart';
+import 'package:inflection/inflection.dart';
+import 'package:recase/recase.dart';
import 'package:source_gen/src/annotation.dart';
import 'package:source_gen/src/utils.dart';
import 'build_context.dart';
@@ -93,6 +95,56 @@ class SQLMigrationGenerator implements Builder {
if (col.nullable != true) buf.write(' NOT NULLABLE');
});
+ // Relations
+ ctx.relationshipFields.forEach((f) {
+ if (i++ > 0) buf.writeln(',');
+ var typeName =
+ f.type.name.startsWith('_') ? f.type.name.substring(1) : f.type.name;
+ var rc = new ReCase(typeName);
+ var relationship = ctx.relationships[f.name];
+
+ if (relationship.type == RelationshipType.BELONGS_TO) {
+ var localKey = relationship.localKey ??
+ (autoSnakeCaseNames != false
+ ? '${rc.snakeCase}_id'
+ : '${typeName}Id');
+ var foreignKey = relationship.foreignKey ?? 'id';
+ var foreignTable = relationship.foreignTable ??
+ (autoSnakeCaseNames != false
+ ? pluralize(rc.snakeCase)
+ : pluralize(typeName));
+ buf.write(' "$localKey" int REFERENCES $foreignTable($foreignKey)');
+ if (relationship.cascadeOnDelete != false)
+ buf.write(' ON DELETE CASCADE');
+ }
+ });
+
+ // Primary keys, unique
+ bool hasPrimary = false;
+ ctx.fields.forEach((f) {
+ var col = ctx.columnInfo[f.name];
+ if (col != null) {
+ var name = ctx.resolveFieldName(f.name);
+ if (col.index == IndexType.UNIQUE) {
+ if (i++ > 0) buf.writeln(',');
+ buf.write(' UNIQUE($name(');
+ } else if (col.index == IndexType.PRIMARY_KEY) {
+ if (i++ > 0) buf.writeln(',');
+ hasPrimary = true;
+ buf.write(' PRIMARY KEY($name)');
+ }
+ }
+ });
+
+ if (!hasPrimary) {
+ var idField =
+ ctx.fields.firstWhere((f) => f.name == 'id', orElse: () => null);
+ if (idField != null) {
+ if (i++ > 0) buf.writeln(',');
+ buf.write(' PRIMARY KEY(id)');
+ }
+ }
+
buf.writeln();
buf.writeln(');');
}
diff --git a/angel_orm_generator/lib/src/builder/postgres/postgres.dart b/angel_orm_generator/lib/src/builder/postgres/postgres.dart
index c490ab05..eeba8b5d 100644
--- a/angel_orm_generator/lib/src/builder/postgres/postgres.dart
+++ b/angel_orm_generator/lib/src/builder/postgres/postgres.dart
@@ -2,8 +2,10 @@ import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:build/build.dart';
+import 'package:code_builder/dart/async.dart';
import 'package:code_builder/dart/core.dart';
import 'package:code_builder/code_builder.dart';
+import 'package:inflection/inflection.dart';
import 'package:path/path.dart' as p;
import 'package:recase/recase.dart';
import 'package:source_gen/src/annotation.dart';
@@ -125,7 +127,9 @@ class PostgresORMGenerator extends GeneratorForAnnotation {
var m = new MethodBuilder('sort$sort', returnType: lib$core.$void);
m.addPositional(parameter('key', [lib$core.String]));
m.addStatement(literal(sort).asAssign(reference('_sortMode')));
- m.addStatement(reference('key').asAssign(reference('_sortKey')));
+ m.addStatement((literal(ctx.prefix) + reference('key'))
+ .parentheses()
+ .asAssign(reference('_sortKey')));
clazz.addMethod(m);
});
@@ -199,8 +203,31 @@ class PostgresORMGenerator extends GeneratorForAnnotation {
return clazz;
}
+ String computeSelector(PostgresBuildContext ctx) {
+ var buf = new StringBuffer();
+ int i = 0;
+
+ // Add all regular fields
+ ctx.fields.forEach((f) {
+ if (i++ > 0) buf.write(', ');
+ var name = ctx.resolveFieldName(f.name);
+ buf.write(ctx.prefix + "$name");
+ });
+
+ // Add all relationship fields...
+ ctx.relationships.forEach((name, r) {
+ var relationship = ctx.populateRelationship(name);
+ relationship.modelTypeContext.fields.forEach((f) {
+ if (i++ > 0) buf.write(', ');
+ var name = relationship.modelTypeContext.resolveFieldName(f.name);
+ buf.write('${relationship.foreignTable}.$name');
+ });
+ });
+
+ return buf.toString();
+ }
+
MethodBuilder buildToSqlMethod(PostgresBuildContext ctx) {
- // TODO: Bake relationships into SQL queries
var meth = new MethodBuilder('toSql', returnType: lib$core.String);
meth.addPositional(parameter('prefix', [lib$core.String]).asOptional());
var buf = reference('buf');
@@ -210,11 +237,27 @@ class PostgresORMGenerator extends GeneratorForAnnotation {
// Write prefix, or default to SELECT
var prefix = reference('prefix');
meth.addStatement(buf.invoke('write', [
- prefix
- .notEquals(literal(null))
- .ternary(prefix, literal('SELECT * FROM "${ctx.tableName}"'))
+ prefix.notEquals(literal(null)).ternary(prefix,
+ literal('SELECT ${computeSelector(ctx)} FROM "${ctx.tableName}"'))
]));
+ var relationsIfThen = ifThen(prefix.equals(literal(null)));
+
+ // Apply relationships
+ ctx.relationships.forEach((name, r) {
+ var relationship = ctx.populateRelationship(name);
+
+ // TODO: Has one, has many, belongs to many
+ if (relationship.type == RelationshipType.BELONGS_TO) {
+ var b = new StringBuffer(
+ ' INNER JOIN ${relationship.foreignTable} ON ${ctx.tableName}.${relationship.localKey} = ${relationship.foreignTable}.${relationship.foreignKey}');
+ relationsIfThen
+ .addStatement(buf.invoke('write', [literal(b.toString())]));
+ }
+ });
+
+ meth.addStatement(relationsIfThen);
+
meth.addStatement(varField('whereClause',
value: reference('where').invoke('toWhereClause', [])));
@@ -308,32 +351,62 @@ class PostgresORMGenerator extends GeneratorForAnnotation {
data[name] = rowKey;
});
- ctx.relationships.forEach((name, relationship) {
- var field = ctx.resolveRelationshipField(name);
- var alias = ctx.resolveFieldName(name);
- var idx = i++;
- var rowKey = row[literal(idx)];
- data[alias] = (row.property('length') < literal(idx + 1)).ternary(
- literal(null),
- new TypeBuilder(new ReCase(field.type.name).pascalCase + 'Query')
- .invoke('parseRow', [rowKey]));
+ // Invoke fromJson()
+ var result = reference('result');
+ meth.addStatement(varField('result',
+ value: ctx.modelClassBuilder
+ .newInstance([map(data)], constructor: 'fromJson')));
+
+ // For each relationship, try to parse
+ ctx.relationships.forEach((name, r) {
+ int minIndex = i;
+
+ var relationship = ctx.populateRelationship(name);
+ var rc = new ReCase(relationship.dartType.name);
+ var relationshipQuery = new TypeBuilder('${rc.pascalCase}Query');
+ List relationshipRow = [];
+
+ relationship.modelTypeContext.fields.forEach((f) {
+ relationshipRow.add(row[literal(i++)]);
+ });
+
+ meth.addStatement(ifThen(row.property('length') > literal(minIndex), [
+ relationshipQuery.invoke(
+ 'parseRow', [list(relationshipRow)]).asAssign(result.property(name))
+ ]));
});
// Then, call a .fromJson() constructor
- meth.addStatement(ctx.modelClassBuilder
- .newInstance([map(data)], constructor: 'fromJson').asReturn());
+ meth.addStatement(result.asReturn());
return meth;
}
- void _invokeStreamClosure(ExpressionBuilder future, MethodBuilder meth) {
+ void _invokeStreamClosure(
+ PostgresBuildContext ctx, ExpressionBuilder future, MethodBuilder meth) {
var ctrl = reference('ctrl');
// Invoke query...
var catchError = ctrl.property('addError');
- var then = new MethodBuilder.closure()..addPositional(parameter('rows'));
- then.addStatement(reference('rows')
- .invoke('map', [reference('parseRow')]).invoke(
- 'forEach', [ctrl.property('add')]));
+ var then = new MethodBuilder.closure(modifier: MethodModifier.asAsync)
+ ..addPositional(parameter('rows'));
+
+ var forEachClosure =
+ new MethodBuilder.closure(modifier: MethodModifier.asAsync);
+ forEachClosure.addPositional(parameter('row'));
+ forEachClosure.addStatement(varField('parsed',
+ value: reference('parseRow').call([reference('row')])));
+ _applyRelationshipsToOutput(
+ ctx, reference('parsed'), reference('row'), forEachClosure);
+ forEachClosure.addStatement(reference('parsed').asReturn());
+
+ then.addStatement(varField('futures',
+ value: reference('rows').invoke('map', [forEachClosure])));
+ then.addStatement(varField('output',
+ value:
+ lib$async.Future.invoke('wait', [reference('futures')]).asAwait()));
+ then.addStatement(
+ reference('output').invoke('forEach', [ctrl.property('add')]));
+
then.addStatement(ctrl.invoke('close', []));
meth.addStatement(
future.invoke('then', [then]).invoke('catchError', [catchError]));
@@ -353,7 +426,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation {
var future =
reference('connection').invoke('query', [reference('toSql').call([])]);
- _invokeStreamClosure(future, meth);
+ _invokeStreamClosure(ctx, future, meth);
return meth;
}
@@ -364,16 +437,23 @@ class PostgresORMGenerator extends GeneratorForAnnotation {
meth.addPositional(parameter('id', [lib$core.int]));
meth.addPositional(
parameter('connection', [ctx.postgreSQLConnectionBuilder]));
- meth.addStatement(reference('connection').invoke('query', [
- literal('SELECT * FROM "${ctx.tableName}" WHERE "id" = @id;')
- ], namedArguments: {
- 'substitutionValues': map({'id': reference('id')})
- }).invoke('then', [
- new MethodBuilder.closure(
- returns:
- reference('parseRow').call([reference('rows').property('first')]))
- ..addPositional(parameter('rows'))
- ]).asReturn());
+
+ var query = reference('query'),
+ whereId = query.property('where').property('id');
+ meth.addStatement(
+ varField('query', value: ctx.queryClassBuilder.newInstance([])));
+ meth.addStatement(whereId.invoke('equals', [reference('id')]));
+
+ // Return null on error
+ var catchErr = new MethodBuilder.closure(returns: literal(null));
+ catchErr.addPositional(parameter('_'));
+
+ meth.addStatement(query
+ .invoke('get', [reference('connection')])
+ .property('first')
+ .invoke('catchError', [catchErr])
+ .asReturn());
+
return meth;
}
@@ -491,7 +571,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation {
$buf.invoke('toString', []) + literal(buf2.toString()),
meth,
substitutionValues);
- _invokeStreamClosure(result, meth);
+ _invokeStreamClosure(ctx, result, meth);
return meth;
}
@@ -514,7 +594,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation {
reference('toSql').call([literal('DELETE FROM "${ctx.tableName}"')]) +
literal(litBuf.toString())
]);
- _invokeStreamClosure(future, meth);
+ _invokeStreamClosure(ctx, future, meth);
return meth;
}
@@ -592,18 +672,71 @@ class PostgresORMGenerator extends GeneratorForAnnotation {
var connection = reference('connection');
var query = literal(buf.toString());
- var result = reference('result');
+ var result = reference('result'), output = reference('output');
meth.addStatement(varField('result',
value: connection.invoke('query', [
query
], namedArguments: {
'substitutionValues': map(substitutionValues)
}).asAwait()));
- meth.addStatement(
- reference('parseRow').call([result[literal(0)]]).asReturn());
+
+ meth.addStatement(varField('output',
+ value: reference('parseRow').call([result[literal(0)]])));
+
+ _applyRelationshipsToOutput(ctx, output, result[literal(0)], meth);
+
+ meth.addStatement(output.asReturn());
return meth;
}
+ void _applyRelationshipsToOutput(PostgresBuildContext ctx,
+ ExpressionBuilder output, ExpressionBuilder row, MethodBuilder meth) {
+ // Every relationship should fill itself in with a query
+ // TODO: Has one, has many, belongs to many
+ ctx.relationships.forEach((name, r) {
+ var relationship = ctx.populateRelationship(name);
+
+ if (relationship.type == RelationshipType.BELONGS_TO) {
+ var rc = new ReCase(relationship.dartType.name);
+ var type = new TypeBuilder('${rc.pascalCase}Query');
+
+ // Resolve index within row...
+ bool matched = false;
+ int col = 0;
+ for (var field in ctx.fields) {
+ if (field is RelationshipConstraintField &&
+ field.originalName == name) {
+ matched = true;
+ break;
+ } else
+ col++;
+ }
+
+ if (!matched)
+ throw 'Couldn\'t resolve row index for relationship "${name}".';
+
+ var idAsInt = row[literal(col)];
+ meth.addStatement(type
+ .invoke('getOne', [idAsInt, reference('connection')])
+ .asAwait()
+ .asAssign(output.property(name)));
+ }
+ });
+ }
+
+ void _addRelationshipConstraintsNamed(
+ MethodBuilder m, PostgresBuildContext ctx) {
+ ctx.relationships.forEach((name, r) {
+ var relationship = ctx.populateRelationship(name);
+
+ // TODO: Belongs to many
+ if (relationship.type == RelationshipType.BELONGS_TO) {
+ var rc = new ReCase(relationship.localKey);
+ m.addNamed(parameter(rc.camelCase, [lib$core.int]));
+ }
+ });
+ }
+
MethodBuilder buildInsertModelMethod(PostgresBuildContext ctx) {
var rc = new ReCase(ctx.modelClassName);
var meth = new MethodBuilder('insert${rc.pascalCase}',
@@ -613,12 +746,17 @@ class PostgresORMGenerator extends GeneratorForAnnotation {
meth.addPositional(
parameter('connection', [ctx.postgreSQLConnectionBuilder]));
meth.addPositional(parameter(rc.camelCase, [ctx.modelClassBuilder]));
+ _addRelationshipConstraintsNamed(meth, ctx);
Map args = {};
var ref = reference(rc.camelCase);
ctx.fields.forEach((f) {
- if (f.name != 'id') args[f.name] = ref.property(f.name);
+ if (f.name != 'id') {
+ args[f.name] = f is RelationshipConstraintField
+ ? reference(f.name)
+ : ref.property(f.name);
+ }
});
meth.addStatement(ctx.queryClassBuilder
@@ -652,7 +790,16 @@ class PostgresORMGenerator extends GeneratorForAnnotation {
// return query.update(connection, ...).first;
Map args = {};
ctx.fields.forEach((f) {
- if (f.name != 'id') args[f.name] = ref.property(f.name);
+ if (f.name != 'id') {
+ if (f is RelationshipConstraintField) {
+ // Need to int.parse the related id and pass it
+ var relation = ref.property(f.originalName);
+ var relationship = ctx.populateRelationship(f.originalName);
+ args[f.name] = lib$core.int
+ .invoke('parse', [relation.property(relationship.foreignKey)]);
+ } else
+ args[f.name] = ref.property(f.name);
+ }
});
var update =
@@ -693,7 +840,8 @@ class PostgresORMGenerator extends GeneratorForAnnotation {
break;
case 'DateTime':
queryBuilderType = new TypeBuilder('DateTimeSqlExpressionBuilder');
- args.add(literal(ctx.resolveFieldName(field.name)));
+ args.add(literal(
+ ctx.tableName + '.' + ctx.resolveFieldName(field.name)));
break;
}
}
@@ -719,9 +867,10 @@ class PostgresORMGenerator extends GeneratorForAnnotation {
ctx.fields.forEach((field) {
var name = ctx.resolveFieldName(field.name);
var queryBuilder = reference(field.name);
- var toAdd = field.type.name == 'DateTime'
+ var toAdd = field.type.isAssignableTo(ctx.dateTimeType)
? queryBuilder.invoke('compile', [])
- : (literal('"$name" ') + queryBuilder.invoke('compile', []));
+ : (literal('${ctx.tableName}.$name ') +
+ queryBuilder.invoke('compile', []));
toWhereClause.addStatement(ifThen(queryBuilder.property('hasValue'), [
expressions.invoke('add', [toAdd])
diff --git a/angel_orm_generator/lib/src/builder/postgres/postgres_build_context.dart b/angel_orm_generator/lib/src/builder/postgres/postgres_build_context.dart
index 289e05e8..ab3e8c79 100644
--- a/angel_orm_generator/lib/src/builder/postgres/postgres_build_context.dart
+++ b/angel_orm_generator/lib/src/builder/postgres/postgres_build_context.dart
@@ -1,3 +1,4 @@
+import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/generated/resolver.dart';
@@ -5,6 +6,10 @@ import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize_generator/context.dart';
import 'package:build/build.dart';
import 'package:code_builder/code_builder.dart';
+import 'package:inflection/inflection.dart';
+import 'package:recase/recase.dart';
+import 'package:source_gen/source_gen.dart';
+import 'build_context.dart';
class PostgresBuildContext extends BuildContext {
DartType _dateTimeTypeCache;
@@ -14,9 +19,12 @@ class PostgresBuildContext extends BuildContext {
_queryClassBuilder,
_whereClassBuilder,
_postgresqlConnectionBuilder;
+ String _prefix;
+ final Map _populatedRelationships = {};
final Map columnInfo = {};
final Map indices = {};
final Map relationships = {};
+ final bool autoSnakeCaseNames, autoIdAndDateFields;
final String tableName;
final ORM ormAnnotation;
final BuildContext raw;
@@ -26,7 +34,7 @@ class PostgresBuildContext extends BuildContext {
PostgresBuildContext(
this.raw, this.ormAnnotation, this.resolver, this.buildStep,
- {this.tableName})
+ {this.tableName, this.autoSnakeCaseNames, this.autoIdAndDateFields})
: super(raw.annotation,
originalClassName: raw.originalClassName,
sourceFilename: raw.sourceFilename);
@@ -45,6 +53,14 @@ class PostgresBuildContext extends BuildContext {
TypeBuilder get postgreSQLConnectionBuilder =>
_postgresqlConnectionBuilder ??= new TypeBuilder('PostgreSQLConnection');
+ String get prefix {
+ if (_prefix != null) return _prefix;
+ if (relationships.isEmpty)
+ return _prefix = '';
+ else
+ return _prefix = tableName + '.';
+ }
+
Map get aliases => raw.aliases;
Map get shimmed => raw.shimmed;
@@ -71,4 +87,110 @@ class PostgresBuildContext extends BuildContext {
FieldElement resolveRelationshipField(String name) =>
relationshipFields.firstWhere((f) => f.name == name, orElse: () => null);
+
+ PopulatedRelationship populateRelationship(String name) {
+ return _populatedRelationships.putIfAbsent(name, () {
+ // TODO: Belongs to many
+ var f = raw.fields.firstWhere((f) => f.name == name);
+ var relationship = relationships[name];
+ var typeName =
+ f.type.name.startsWith('_') ? f.type.name.substring(1) : f.type.name;
+ var rc = new ReCase(typeName);
+
+ if (relationship.type == RelationshipType.HAS_ONE ||
+ relationship.type == RelationshipType.HAS_MANY) {
+ var foreignKey = relationship.localKey ??
+ (autoSnakeCaseNames != false
+ ? '${rc.snakeCase}_id'
+ : '${typeName}Id');
+ var localKey = relationship.foreignKey ?? 'id';
+ var foreignTable = relationship.foreignTable ??
+ (autoSnakeCaseNames != false
+ ? pluralize(rc.snakeCase)
+ : pluralize(typeName));
+ return new PopulatedRelationship(relationship.type, f.type, buildStep,
+ resolver, autoSnakeCaseNames, autoIdAndDateFields,
+ localKey: localKey,
+ foreignKey: foreignKey,
+ foreignTable: foreignTable,
+ cascadeOnDelete: relationship.cascadeOnDelete);
+ } else if (relationship.type == RelationshipType.BELONGS_TO) {
+ var localKey = relationship.localKey ??
+ (autoSnakeCaseNames != false
+ ? '${rc.snakeCase}_id'
+ : '${typeName}Id');
+ var foreignKey = relationship.foreignKey ?? 'id';
+ var foreignTable = relationship.foreignTable ??
+ (autoSnakeCaseNames != false
+ ? pluralize(rc.snakeCase)
+ : pluralize(typeName));
+ return new PopulatedRelationship(relationship.type, f.type, buildStep,
+ resolver, autoSnakeCaseNames, autoIdAndDateFields,
+ localKey: localKey,
+ foreignKey: foreignKey,
+ foreignTable: foreignTable,
+ cascadeOnDelete: relationship.cascadeOnDelete);
+ } else
+ throw new UnsupportedError(
+ 'Invalid relationship type: ${relationship.type}');
+ });
+ }
+}
+
+class PopulatedRelationship extends Relationship {
+ DartType _modelType;
+ PostgresBuildContext _modelTypeContext;
+ DartObject _modelTypeORM;
+ final DartType dartType;
+ final BuildStep buildStep;
+ final Resolver resolver;
+ final bool autoSnakeCaseNames, autoIdAndDateFields;
+
+ PopulatedRelationship(int type, this.dartType, this.buildStep, this.resolver,
+ this.autoSnakeCaseNames, this.autoIdAndDateFields,
+ {String localKey,
+ String foreignKey,
+ String foreignTable,
+ bool cascadeOnDelete})
+ : super(type,
+ localKey: localKey,
+ foreignKey: foreignKey,
+ foreignTable: foreignTable,
+ cascadeOnDelete: cascadeOnDelete);
+
+ DartType get modelType {
+ if (_modelType != null) return _modelType;
+ DartType searchType = dartType;
+ var ormChecker = new TypeChecker.fromRuntime(ORM);
+
+ while (searchType != null) {
+ var classElement = searchType.element as ClassElement;
+ var ormAnnotation = ormChecker.firstAnnotationOf(classElement);
+
+ if (ormAnnotation != null) {
+ _modelTypeORM = ormAnnotation;
+ return _modelType = searchType;
+ } else {
+ // If we didn't find an @ORM(), then refer to the parent type.
+ searchType = classElement.supertype;
+ }
+ }
+
+ throw new StateError(
+ 'Neither ${dartType.name} nor its parent types are annotated with an @ORM() annotation. It is impossible to compute this relationship.');
+ }
+
+ PostgresBuildContext get modelTypeContext {
+ if (_modelTypeContext != null) return _modelTypeContext;
+ var reader = new ConstantReader(_modelTypeORM);
+ if (reader.isNull)
+ reader = null;
+ else
+ reader = reader.read('tableName');
+ var orm = reader == null
+ ? new ORM()
+ : new ORM(reader.isString ? reader.stringValue : null);
+ return _modelTypeContext = buildContext(modelType.element, orm, buildStep,
+ resolver, autoSnakeCaseNames, autoIdAndDateFields);
+ }
}
diff --git a/angel_orm_generator/pubspec.yaml b/angel_orm_generator/pubspec.yaml
index 0bb44716..c947205b 100644
--- a/angel_orm_generator/pubspec.yaml
+++ b/angel_orm_generator/pubspec.yaml
@@ -1,5 +1,5 @@
name: angel_orm_generator
-version: 1.0.0-alpha+1
+version: 1.0.0-alpha+2
description: Code generators for Angel's ORM.
author: Tobe O
homepage: https://github.com/angel-dart/orm
diff --git a/angel_orm_generator/test/book_test.dart b/angel_orm_generator/test/book_test.dart
new file mode 100644
index 00000000..9bddb05c
--- /dev/null
+++ b/angel_orm_generator/test/book_test.dart
@@ -0,0 +1,135 @@
+import 'package:angel_orm/angel_orm.dart';
+import 'package:postgres/postgres.dart';
+import 'package:test/test.dart';
+import 'models/author.dart';
+import 'models/author.orm.g.dart';
+import 'models/book.dart';
+import 'models/book.orm.g.dart';
+import 'common.dart';
+
+main() {
+ PostgreSQLConnection connection;
+ Author rowling;
+ Book deathlyHallows;
+
+ setUp(() async {
+ connection = await connectToPostgres();
+
+ // Insert an author
+ rowling = await AuthorQuery.insert(connection, name: 'J.K. Rowling');
+
+ // And a book
+ deathlyHallows = await BookQuery.insert(connection,
+ authorId: int.parse(rowling.id), name: 'Deathly Hallows');
+ });
+
+ tearDown(() => connection.close());
+
+ group('selects', ()
+ {
+ test('select all', () async {
+ var query = new BookQuery();
+ var books = await query.get(connection).toList();
+ expect(books, hasLength(1));
+
+ var book = books.first;
+ print(book.toJson());
+ expect(book.id, deathlyHallows.id);
+ expect(book.name, deathlyHallows.name);
+
+ var author = book.author as Author;
+ print(author.toJson());
+ expect(author.id, rowling.id);
+ expect(author.name, rowling.name);
+ });
+
+ test('select one', () async {
+ var query = new BookQuery();
+ query.where.id.equals(int.parse(deathlyHallows.id));
+ print(query.toSql());
+
+ var book = await BookQuery.getOne(
+ int.parse(deathlyHallows.id), connection);
+ print(book.toJson());
+ expect(book.id, deathlyHallows.id);
+ expect(book.name, deathlyHallows.name);
+
+ var author = book.author as Author;
+ print(author.toJson());
+ expect(author.id, rowling.id);
+ expect(author.name, rowling.name);
+ });
+
+ test('where clause', () async {
+ var query = new BookQuery()
+ ..where.name.equals('Goblet of Fire')
+ ..or(new BookQueryWhere()..authorId.equals(int.parse(rowling.id)));
+ print(query.toSql());
+
+ var books = await query.get(connection).toList();
+ expect(books, hasLength(1));
+
+ var book = books.first;
+ print(book.toJson());
+ expect(book.id, deathlyHallows.id);
+ expect(book.name, deathlyHallows.name);
+
+ var author = book.author as Author;
+ print(author.toJson());
+ expect(author.id, rowling.id);
+ expect(author.name, rowling.name);
+ });
+
+ test('union', () async {
+ var query1 = new BookQuery()
+ ..where.name.like('Deathly%');
+ var query2 = new BookQuery()
+ ..where.authorId.equals(-1);
+ var query3 = new BookQuery()
+ ..where.name.isIn(['Goblet of Fire', 'Order of the Phoenix']);
+ query1
+ ..union(query2)
+ ..unionAll(query3);
+ print(query1.toSql());
+
+ var books = await query1.get(connection).toList();
+ expect(books, hasLength(1));
+
+ var book = books.first;
+ print(book.toJson());
+ expect(book.id, deathlyHallows.id);
+ expect(book.name, deathlyHallows.name);
+
+ var author = book.author as Author;
+ print(author.toJson());
+ expect(author.id, rowling.id);
+ expect(author.name, rowling.name);
+ });
+ });
+
+ test('insert sets relationship', () {
+ expect(deathlyHallows.author, isNotNull);
+ expect((deathlyHallows.author as Author).name, rowling.name);
+ });
+
+ test('delete stream', () async {
+ var query = new BookQuery()..where.name.equals(deathlyHallows.name);
+ print(query.toSql());
+ var books = await query.delete(connection).toList();
+ expect(books, hasLength(1));
+
+ var book = books.first;
+ expect(book.id, deathlyHallows.id);
+ expect(book.author, isNotNull);
+ expect((book.author as Author).name, rowling.name);
+ });
+
+ test('update book', () async {
+ var cloned = deathlyHallows.clone()..name = 'Sorcerer\'s Stone';
+ var book = await BookQuery.updateBook(connection, cloned);
+ print(book.toJson());
+ expect(book.name, cloned.name);
+ expect(book.author, isNotNull);
+ expect((book.author as Author).name, rowling.name);
+ });
+}
diff --git a/angel_orm_generator/test/car_test.dart b/angel_orm_generator/test/car_test.dart
index 7f3d0a98..9381c477 100644
--- a/angel_orm_generator/test/car_test.dart
+++ b/angel_orm_generator/test/car_test.dart
@@ -16,7 +16,7 @@ main() {
var whereClause = query.where.toWhereClause();
print('Where clause: $whereClause');
expect(whereClause,
- 'WHERE "family_friendly" = TRUE AND "recalled_at" <= \'2000-01-01\'');
+ 'WHERE cars.family_friendly = TRUE AND cars.recalled_at <= \'2000-01-01\'');
});
test('parseRow', () {
diff --git a/angel_orm_generator/test/common.dart b/angel_orm_generator/test/common.dart
index ab0e2eae..ac4417c1 100644
--- a/angel_orm_generator/test/common.dart
+++ b/angel_orm_generator/test/common.dart
@@ -10,6 +10,10 @@ Future connectToPostgres() async {
var query = await new File('test/models/car.up.g.sql').readAsString();
await conn.execute(query);
+ query = await new File('test/models/author.up.g.sql').readAsString();
+ await conn.execute(query);
+ query = await new File('test/models/book.up.g.sql').readAsString();
+ await conn.execute(query);
return conn;
}
diff --git a/angel_orm_generator/test/models/author.orm.g.dart b/angel_orm_generator/test/models/author.orm.g.dart
index 02f38433..97544260 100644
--- a/angel_orm_generator/test/models/author.orm.g.dart
+++ b/angel_orm_generator/test/models/author.orm.g.dart
@@ -35,12 +35,12 @@ class AuthorQuery {
void sortDescending(String key) {
_sortMode = 'Descending';
- _sortKey = key;
+ _sortKey = ('' + key);
}
void sortAscending(String key) {
_sortMode = 'Ascending';
- _sortKey = key;
+ _sortKey = ('' + key);
}
void or(AuthorQueryWhere selector) {
@@ -49,7 +49,10 @@ class AuthorQuery {
String toSql([String prefix]) {
var buf = new StringBuffer();
- buf.write(prefix != null ? prefix : 'SELECT * FROM "authors"');
+ buf.write(prefix != null
+ ? prefix
+ : 'SELECT id, name, created_at, updated_at FROM "authors"');
+ if (prefix == null) {}
var whereClause = where.toWhereClause();
if (whereClause != null) {
buf.write(' ' + whereClause);
@@ -88,26 +91,33 @@ class AuthorQuery {
}
static Author parseRow(List row) {
- return new Author.fromJson({
+ var result = new Author.fromJson({
'id': row[0].toString(),
'name': row[1],
'created_at': row[2],
'updated_at': row[3]
});
+ return result;
}
Stream get(PostgreSQLConnection connection) {
StreamController ctrl = new StreamController();
- connection.query(toSql()).then((rows) {
- rows.map(parseRow).forEach(ctrl.add);
+ 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 getOne(int id, PostgreSQLConnection connection) {
- return connection.query('SELECT * FROM "authors" WHERE "id" = @id;',
- substitutionValues: {'id': id}).then((rows) => parseRow(rows.first));
+ var query = new AuthorQuery();
+ query.where.id.equals(id);
+ return query.get(connection).first.catchError((_) => null);
}
Stream update(PostgreSQLConnection connection,
@@ -128,8 +138,13 @@ class AuthorQuery {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
- }).then((rows) {
- rows.map(parseRow).forEach(ctrl.add);
+ }).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;
@@ -140,8 +155,13 @@ class AuthorQuery {
connection
.query(toSql('DELETE FROM "authors"') +
' RETURNING "id", "name", "created_at", "updated_at";')
- .then((rows) {
- rows.map(parseRow).forEach(ctrl.add);
+ .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;
@@ -165,7 +185,8 @@ class AuthorQuery {
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
});
- return parseRow(result[0]);
+ var output = parseRow(result[0]);
+ return output;
}
static Future insertAuthor(
@@ -199,18 +220,18 @@ class AuthorQueryWhere {
final StringSqlExpressionBuilder name = new StringSqlExpressionBuilder();
final DateTimeSqlExpressionBuilder createdAt =
- new DateTimeSqlExpressionBuilder('created_at');
+ new DateTimeSqlExpressionBuilder('authors.created_at');
final DateTimeSqlExpressionBuilder updatedAt =
- new DateTimeSqlExpressionBuilder('updated_at');
+ new DateTimeSqlExpressionBuilder('authors.updated_at');
String toWhereClause({bool keyword}) {
final List expressions = [];
if (id.hasValue) {
- expressions.add('"id" ' + id.compile());
+ expressions.add('authors.id ' + id.compile());
}
if (name.hasValue) {
- expressions.add('"name" ' + name.compile());
+ expressions.add('authors.name ' + name.compile());
}
if (createdAt.hasValue) {
expressions.add(createdAt.compile());
diff --git a/angel_orm_generator/test/models/author.up.g.sql b/angel_orm_generator/test/models/author.up.g.sql
index d893e0ce..802e83a0 100644
--- a/angel_orm_generator/test/models/author.up.g.sql
+++ b/angel_orm_generator/test/models/author.up.g.sql
@@ -2,5 +2,6 @@ CREATE TEMPORARY TABLE "authors" (
"id" serial,
"name" varchar,
"created_at" timestamp,
- "updated_at" timestamp
+ "updated_at" timestamp,
+ PRIMARY KEY(id)
);
diff --git a/angel_orm_generator/test/models/book.dart b/angel_orm_generator/test/models/book.dart
index 392438da..9a153936 100644
--- a/angel_orm_generator/test/models/book.dart
+++ b/angel_orm_generator/test/models/book.dart
@@ -1,4 +1,4 @@
-library angel_orm.test.models.author;
+library angel_orm.test.models.book;
import 'package:angel_framework/common.dart';
import 'package:angel_orm/angel_orm.dart';
diff --git a/angel_orm_generator/test/models/book.g.dart b/angel_orm_generator/test/models/book.g.dart
index 7f2c01df..9360456c 100644
--- a/angel_orm_generator/test/models/book.g.dart
+++ b/angel_orm_generator/test/models/book.g.dart
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
-part of angel_orm.test.models.author;
+part of angel_orm.test.models.book;
// **************************************************************************
// Generator: JsonModelGenerator
diff --git a/angel_orm_generator/test/models/book.orm.g.dart b/angel_orm_generator/test/models/book.orm.g.dart
index a3dde4ba..d1339a39 100644
--- a/angel_orm_generator/test/models/book.orm.g.dart
+++ b/angel_orm_generator/test/models/book.orm.g.dart
@@ -36,12 +36,12 @@ class BookQuery {
void sortDescending(String key) {
_sortMode = 'Descending';
- _sortKey = key;
+ _sortKey = ('books.' + key);
}
void sortAscending(String key) {
_sortMode = 'Ascending';
- _sortKey = key;
+ _sortKey = ('books.' + key);
}
void or(BookQueryWhere selector) {
@@ -50,7 +50,12 @@ class BookQuery {
String toSql([String prefix]) {
var buf = new StringBuffer();
- buf.write(prefix != null ? prefix : 'SELECT * FROM "books"');
+ buf.write(prefix != null
+ ? prefix
+ : 'SELECT books.id, books.name, books.created_at, books.updated_at, books.author_id, authors.id, authors.name, authors.created_at, authors.updated_at FROM "books"');
+ if (prefix == null) {
+ buf.write(' INNER JOIN authors ON books.author_id = authors.id');
+ }
var whereClause = where.toWhereClause();
if (whereClause != null) {
buf.write(' ' + whereClause);
@@ -89,33 +94,44 @@ class BookQuery {
}
static Book parseRow(List row) {
- return new Book.fromJson({
+ var result = new Book.fromJson({
'id': row[0].toString(),
'name': row[1],
'created_at': row[2],
'updated_at': row[3],
- 'author': row.length < 5 ? null : AuthorQuery.parseRow(row[4])
+ 'author_id': row[4]
});
+ if (row.length > 5) {
+ result.author = AuthorQuery.parseRow([row[5], row[6], row[7], row[8]]);
+ }
+ return result;
}
Stream get(PostgreSQLConnection connection) {
StreamController ctrl = new StreamController();
- connection.query(toSql()).then((rows) {
- rows.map(parseRow).forEach(ctrl.add);
+ connection.query(toSql()).then((rows) async {
+ var futures = rows.map((row) async {
+ var parsed = parseRow(row);
+ parsed.author = await AuthorQuery.getOne(row[4], connection);
+ return parsed;
+ });
+ var output = await Future.wait(futures);
+ output.forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
static Future getOne(int id, PostgreSQLConnection connection) {
- return connection.query('SELECT * FROM "books" WHERE "id" = @id;',
- substitutionValues: {'id': id}).then((rows) => parseRow(rows.first));
+ var query = new BookQuery();
+ query.where.id.equals(id);
+ return query.get(connection).first.catchError((_) => null);
}
Stream update(PostgreSQLConnection connection,
- {String name, DateTime createdAt, DateTime updatedAt}) {
+ {String name, DateTime createdAt, DateTime updatedAt, int authorId}) {
var buf = new StringBuffer(
- 'UPDATE "books" SET ("name", "created_at", "updated_at") = (@name, @createdAt, @updatedAt) ');
+ 'UPDATE "books" SET ("name", "created_at", "updated_at", "author_id") = (@name, @createdAt, @updatedAt, @authorId) ');
var whereClause = where.toWhereClause();
if (whereClause == null) {
buf.write('WHERE "id" = @id');
@@ -125,13 +141,21 @@ class BookQuery {
var __ormNow__ = new DateTime.now();
var ctrl = new StreamController();
connection.query(
- buf.toString() + ' RETURNING "id", "name", "created_at", "updated_at";',
+ buf.toString() +
+ ' RETURNING "id", "name", "created_at", "updated_at", "author_id";',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
- 'updatedAt': updatedAt != null ? updatedAt : __ormNow__
- }).then((rows) {
- rows.map(parseRow).forEach(ctrl.add);
+ 'updatedAt': updatedAt != null ? updatedAt : __ormNow__,
+ 'authorId': authorId
+ }).then((rows) async {
+ var futures = rows.map((row) async {
+ var parsed = parseRow(row);
+ parsed.author = await AuthorQuery.getOne(row[4], connection);
+ return parsed;
+ });
+ var output = await Future.wait(futures);
+ output.forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
@@ -141,9 +165,15 @@ class BookQuery {
StreamController ctrl = new StreamController();
connection
.query(toSql('DELETE FROM "books"') +
- ' RETURNING "id", "name", "created_at", "updated_at";')
- .then((rows) {
- rows.map(parseRow).forEach(ctrl.add);
+ ' RETURNING "id", "name", "created_at", "updated_at", "author_id";')
+ .then((rows) async {
+ var futures = rows.map((row) async {
+ var parsed = parseRow(row);
+ parsed.author = await AuthorQuery.getOne(row[4], connection);
+ return parsed;
+ });
+ var output = await Future.wait(futures);
+ output.forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
@@ -151,27 +181,37 @@ class BookQuery {
static Future deleteOne(int id, PostgreSQLConnection connection) async {
var result = await connection.query(
- 'DELETE FROM "books" WHERE id = @id RETURNING "id", "name", "created_at", "updated_at";',
+ 'DELETE FROM "books" WHERE id = @id RETURNING "id", "name", "created_at", "updated_at", "author_id";',
substitutionValues: {'id': id});
return parseRow(result[0]);
}
static Future insert(PostgreSQLConnection connection,
- {String name, DateTime createdAt, DateTime updatedAt}) async {
+ {String name,
+ DateTime createdAt,
+ DateTime updatedAt,
+ int authorId}) async {
var __ormNow__ = new DateTime.now();
var result = await connection.query(
- 'INSERT INTO "books" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt) RETURNING "id", "name", "created_at", "updated_at";',
+ 'INSERT INTO "books" ("name", "created_at", "updated_at", "author_id") VALUES (@name, @createdAt, @updatedAt, @authorId) RETURNING "id", "name", "created_at", "updated_at", "author_id";',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
- 'updatedAt': updatedAt != null ? updatedAt : __ormNow__
+ 'updatedAt': updatedAt != null ? updatedAt : __ormNow__,
+ 'authorId': authorId
});
- return parseRow(result[0]);
+ var output = parseRow(result[0]);
+ output.author = await AuthorQuery.getOne(result[0][4], connection);
+ return output;
}
- static Future insertBook(PostgreSQLConnection connection, Book book) {
+ static Future insertBook(PostgreSQLConnection connection, Book book,
+ {int authorId}) {
return BookQuery.insert(connection,
- name: book.name, createdAt: book.createdAt, updatedAt: book.updatedAt);
+ name: book.name,
+ createdAt: book.createdAt,
+ updatedAt: book.updatedAt,
+ authorId: authorId);
}
static Future updateBook(PostgreSQLConnection connection, Book book) {
@@ -181,7 +221,8 @@ class BookQuery {
.update(connection,
name: book.name,
createdAt: book.createdAt,
- updatedAt: book.updatedAt)
+ updatedAt: book.updatedAt,
+ authorId: int.parse(book.author.id))
.first;
}
@@ -196,18 +237,21 @@ class BookQueryWhere {
final StringSqlExpressionBuilder name = new StringSqlExpressionBuilder();
final DateTimeSqlExpressionBuilder createdAt =
- new DateTimeSqlExpressionBuilder('created_at');
+ new DateTimeSqlExpressionBuilder('books.created_at');
final DateTimeSqlExpressionBuilder updatedAt =
- new DateTimeSqlExpressionBuilder('updated_at');
+ new DateTimeSqlExpressionBuilder('books.updated_at');
+
+ final NumericSqlExpressionBuilder authorId =
+ new NumericSqlExpressionBuilder();
String toWhereClause({bool keyword}) {
final List expressions = [];
if (id.hasValue) {
- expressions.add('"id" ' + id.compile());
+ expressions.add('books.id ' + id.compile());
}
if (name.hasValue) {
- expressions.add('"name" ' + name.compile());
+ expressions.add('books.name ' + name.compile());
}
if (createdAt.hasValue) {
expressions.add(createdAt.compile());
@@ -215,6 +259,9 @@ class BookQueryWhere {
if (updatedAt.hasValue) {
expressions.add(updatedAt.compile());
}
+ if (authorId.hasValue) {
+ expressions.add('books.author_id ' + authorId.compile());
+ }
return expressions.isEmpty
? null
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
diff --git a/angel_orm_generator/test/models/book.up.g.sql b/angel_orm_generator/test/models/book.up.g.sql
index 26689827..465e1f07 100644
--- a/angel_orm_generator/test/models/book.up.g.sql
+++ b/angel_orm_generator/test/models/book.up.g.sql
@@ -2,5 +2,7 @@ CREATE TEMPORARY TABLE "books" (
"id" serial,
"name" varchar,
"created_at" timestamp,
- "updated_at" timestamp
+ "updated_at" timestamp,
+ "author_id" int REFERENCES authors(id) ON DELETE CASCADE,
+ PRIMARY KEY(id)
);
diff --git a/angel_orm_generator/test/models/car.orm.g.dart b/angel_orm_generator/test/models/car.orm.g.dart
index ab2b5422..df03bed7 100644
--- a/angel_orm_generator/test/models/car.orm.g.dart
+++ b/angel_orm_generator/test/models/car.orm.g.dart
@@ -35,12 +35,12 @@ class CarQuery {
void sortDescending(String key) {
_sortMode = 'Descending';
- _sortKey = key;
+ _sortKey = ('' + key);
}
void sortAscending(String key) {
_sortMode = 'Ascending';
- _sortKey = key;
+ _sortKey = ('' + key);
}
void or(CarQueryWhere selector) {
@@ -49,7 +49,10 @@ class CarQuery {
String toSql([String prefix]) {
var buf = new StringBuffer();
- buf.write(prefix != null ? prefix : 'SELECT * FROM "cars"');
+ buf.write(prefix != null
+ ? prefix
+ : 'SELECT id, make, description, family_friendly, recalled_at, created_at, updated_at FROM "cars"');
+ if (prefix == null) {}
var whereClause = where.toWhereClause();
if (whereClause != null) {
buf.write(' ' + whereClause);
@@ -88,7 +91,7 @@ class CarQuery {
}
static Car parseRow(List row) {
- return new Car.fromJson({
+ var result = new Car.fromJson({
'id': row[0].toString(),
'make': row[1],
'description': row[2],
@@ -97,20 +100,27 @@ class CarQuery {
'created_at': row[5],
'updated_at': row[6]
});
+ return result;
}
Stream get(PostgreSQLConnection connection) {
StreamController ctrl = new StreamController();
- connection.query(toSql()).then((rows) {
- rows.map(parseRow).forEach(ctrl.add);
+ 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 getOne(int id, PostgreSQLConnection connection) {
- return connection.query('SELECT * FROM "cars" WHERE "id" = @id;',
- substitutionValues: {'id': id}).then((rows) => parseRow(rows.first));
+ var query = new CarQuery();
+ query.where.id.equals(id);
+ return query.get(connection).first.catchError((_) => null);
}
Stream update(PostgreSQLConnection connection,
@@ -140,8 +150,13 @@ class CarQuery {
'recalledAt': recalledAt,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
- }).then((rows) {
- rows.map(parseRow).forEach(ctrl.add);
+ }).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;
@@ -152,8 +167,13 @@ class CarQuery {
connection
.query(toSql('DELETE FROM "cars"') +
' RETURNING "id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at";')
- .then((rows) {
- rows.map(parseRow).forEach(ctrl.add);
+ .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;
@@ -184,7 +204,8 @@ class CarQuery {
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
});
- return parseRow(result[0]);
+ var output = parseRow(result[0]);
+ return output;
}
static Future insertCar(PostgreSQLConnection connection, Car car) {
@@ -228,27 +249,27 @@ class CarQueryWhere {
new BooleanSqlExpressionBuilder();
final DateTimeSqlExpressionBuilder recalledAt =
- new DateTimeSqlExpressionBuilder('recalled_at');
+ new DateTimeSqlExpressionBuilder('cars.recalled_at');
final DateTimeSqlExpressionBuilder createdAt =
- new DateTimeSqlExpressionBuilder('created_at');
+ new DateTimeSqlExpressionBuilder('cars.created_at');
final DateTimeSqlExpressionBuilder updatedAt =
- new DateTimeSqlExpressionBuilder('updated_at');
+ new DateTimeSqlExpressionBuilder('cars.updated_at');
String toWhereClause({bool keyword}) {
final List expressions = [];
if (id.hasValue) {
- expressions.add('"id" ' + id.compile());
+ expressions.add('cars.id ' + id.compile());
}
if (make.hasValue) {
- expressions.add('"make" ' + make.compile());
+ expressions.add('cars.make ' + make.compile());
}
if (description.hasValue) {
- expressions.add('"description" ' + description.compile());
+ expressions.add('cars.description ' + description.compile());
}
if (familyFriendly.hasValue) {
- expressions.add('"family_friendly" ' + familyFriendly.compile());
+ expressions.add('cars.family_friendly ' + familyFriendly.compile());
}
if (recalledAt.hasValue) {
expressions.add(recalledAt.compile());
diff --git a/angel_orm_generator/test/models/car.up.g.sql b/angel_orm_generator/test/models/car.up.g.sql
index bbf81d26..4cfacf16 100644
--- a/angel_orm_generator/test/models/car.up.g.sql
+++ b/angel_orm_generator/test/models/car.up.g.sql
@@ -5,5 +5,6 @@ CREATE TEMPORARY TABLE "cars" (
"family_friendly" boolean,
"recalled_at" timestamp,
"created_at" timestamp,
- "updated_at" timestamp
+ "updated_at" timestamp,
+ PRIMARY KEY(id)
);
diff --git a/angel_orm_generator/tool/phases.dart b/angel_orm_generator/tool/phases.dart
index 78c2dd59..40103de4 100644
--- a/angel_orm_generator/tool/phases.dart
+++ b/angel_orm_generator/tool/phases.dart
@@ -3,16 +3,28 @@ import 'package:source_gen/source_gen.dart';
import 'package:angel_orm_generator/angel_orm_generator.dart';
import 'package:angel_serialize_generator/angel_serialize_generator.dart';
-final InputSet MODELS =
+final InputSet ALL_MODELS =
new InputSet('angel_orm_generator', const ['test/models/*.dart']);
+final InputSet STANDALONE_MODELS = new InputSet('angel_orm_generator',
+ const ['test/models/car.dart', 'test/models/author.dart']);
+final InputSet DEPENDENT_MODELS =
+ new InputSet('angel_orm_generator', const ['test/models/book.dart']);
final PhaseGroup PHASES = new PhaseGroup()
..addPhase(new Phase()
- ..addAction(new GeneratorBuilder([const JsonModelGenerator()]), MODELS))
+ ..addAction(
+ new GeneratorBuilder([const JsonModelGenerator()]), STANDALONE_MODELS)
+ ..addAction(
+ new GeneratorBuilder([const JsonModelGenerator()]), DEPENDENT_MODELS))
..addPhase(new Phase()
..addAction(
new GeneratorBuilder([new PostgresORMGenerator()],
isStandalone: true, generatedExtension: '.orm.g.dart'),
- MODELS))
+ STANDALONE_MODELS))
..addPhase(new Phase()
- ..addAction(new SQLMigrationGenerator(temporary: true), MODELS));
+ ..addAction(
+ new GeneratorBuilder([new PostgresORMGenerator()],
+ isStandalone: true, generatedExtension: '.orm.g.dart'),
+ DEPENDENT_MODELS))
+ ..addPhase(new Phase()
+ ..addAction(new SQLMigrationGenerator(temporary: true), ALL_MODELS));