Start service gen

This commit is contained in:
thosakwe 2017-08-03 14:42:01 -04:00
parent f9de817e40
commit abf438196e
7 changed files with 186 additions and 1484 deletions

View file

@ -1,2 +1,3 @@
export 'src/builder/postgres/migration.dart'; export 'src/builder/orm/migration.dart';
export 'src/builder/postgres/postgres.dart'; export 'src/builder/orm/postgres.dart';
export 'src/builder/orm/service.dart';

View file

@ -0,0 +1,178 @@
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';
import 'package:source_gen/src/utils.dart';
import 'package:source_gen/source_gen.dart';
import 'build_context.dart';
import 'postgres_build_context.dart';
class PostgresServiceGenerator extends GeneratorForAnnotation<ORM> {
final bool autoSnakeCaseNames;
final bool autoIdAndDateFields;
const PostgresServiceGenerator(
{this.autoSnakeCaseNames: true, this.autoIdAndDateFields: true});
@override
Future<String> generateForAnnotatedElement(
Element element, ORM annotation, BuildStep buildStep) async {
if (buildStep.inputId.path.contains('.service.g.dart')) {
return null;
}
if (element is! ClassElement)
throw 'Only classes can be annotated with @ORM().';
var resolver = await buildStep.resolver;
var lib =
generateOrmLibrary(element.library, resolver, buildStep).buildAst();
if (lib == null) return null;
return prettyToSource(lib);
}
LibraryBuilder generateOrmLibrary(
LibraryElement libraryElement, Resolver resolver, BuildStep buildStep) {
var lib = new LibraryBuilder();
lib.addDirective(new ImportBuilder('dart:async'));
lib.addDirective(
new ImportBuilder('package:angel_framework/angel_framework.dart'));
lib.addDirective(new ImportBuilder('package:postgres/postgres.dart'));
lib.addDirective(new ImportBuilder(p.basename(buildStep.inputId.path)));
var pathName = p.basenameWithoutExtension(
p.basenameWithoutExtension(buildStep.inputId.path));
lib.addDirective(new ImportBuilder('$pathName.orm.g.dart'));
var elements = getElementsFromLibraryElement(libraryElement)
.where((el) => el is ClassElement);
Map<ClassElement, PostgresBuildContext> contexts = {};
List<String> done = [];
for (var element in elements) {
if (!done.contains(element.name)) {
var ann = element.metadata
.firstWhere((a) => matchAnnotation(ORM, a), orElse: () => null);
if (ann != null) {
contexts[element] = buildContext(
element,
instantiateAnnotation(ann),
buildStep,
resolver,
autoSnakeCaseNames != false,
autoIdAndDateFields != false);
}
}
}
if (contexts.isEmpty) return null;
done.clear();
for (var element in contexts.keys) {
if (!done.contains(element.name)) {
var ctx = contexts[element];
lib.addMember(buildServiceClass(ctx));
done.add(element.name);
}
}
return lib;
}
ClassBuilder buildServiceClass(PostgresBuildContext ctx) {
var rc = new ReCase(ctx.modelClassName);
var clazz = new ClassBuilder('${rc.pascalCase}Service',
asExtends: new TypeBuilder('Service'));
// Add fields
// connection, allowRemoveAll, allowQuery
clazz
..addField(varFinal('connection', type: ctx.postgreSQLConnectionBuilder))
..addField(varFinal('allowRemoveAll', type: lib$core.bool))
..addField(varFinal('allowQuery', type: lib$core.bool));
clazz.addConstructor(constructor([
thisField(parameter('connection')),
thisField(named(parameter('allowRemoveAll', [literal(false)]))),
thisField(named(parameter('allowQuery', [literal(false)])))
]));
clazz.addMethod(buildQueryMethod(ctx));
clazz.addMethod(buildToIdMethod(ctx));
var params = reference('params'),
buildQuery = reference('buildQuery'),
connection = reference('connection'),
query = reference('query');
// Future<List<T>> index([p]) => buildQuery(p).get(connection).toList();
clazz.addMethod(lambda(
'index',
buildQuery
.call([params]).invoke('get', [connection]).invoke('toList', []),
returnType: new TypeBuilder('Future', genericTypes: [
new TypeBuilder('List', genericTypes: [ctx.modelClassBuilder])
]))
..addPositional(parameter('params', [lib$core.Map]))
..addAnnotation(lib$core.override));
var read = new MethodBuilder('read',
returnType:
new TypeBuilder('Future', genericTypes: [ctx.modelClassBuilder]));
parseParams(read, ctx, id: true);
read.addStatement(query.invoke('get', [connection]).property('first'));
clazz.addMethod(read);
return clazz;
}
MethodBuilder buildQueryMethod(PostgresBuildContext ctx) {
var meth =
new MethodBuilder('buildQuery', returnType: ctx.queryClassBuilder);
return meth;
}
MethodBuilder buildToIdMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('toId', returnType: lib$core.int);
var id = reference('id');
meth.addStatement(ifThen(id.isInstanceOf(lib$core.int), [
id.asReturn(),
elseThen([
ifThen(id.equals(literal('null')).or(id.equals(literal(null))), [
literal(null).asReturn(),
elseThen([
lib$core.int.invoke('parse', [id.invoke('toString', [])]).asReturn()
])
])
])
]));
return meth;
}
void parseParams(MethodBuilder meth, PostgresBuildContext ctx, {bool id}) {
meth.addStatement(varField('query',
value: reference('buildQuery').call([
reference('params')
.notEquals(literal(null))
.ternary(reference('params'), map({}))
])));
if (id == true) {
meth.addStatement(
reference('query').property('where').property('id').invoke('equals', [
reference('toId').call([reference('id')])
]));
}
}
}

View file

@ -1,141 +0,0 @@
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;
import 'package:angel_serialize_generator/context.dart' as serialize;
import 'package:build/build.dart';
import 'package:inflection/inflection.dart';
import 'package:recase/recase.dart';
import 'package:source_gen/source_gen.dart';
import 'postgres_build_context.dart';
PostgresBuildContext buildContext(
ClassElement clazz,
ORM annotation,
BuildStep buildStep,
Resolver resolver,
bool autoSnakeCaseNames,
bool autoIdAndDateFields) {
var raw = serialize.buildContext(clazz, null, buildStep, resolver,
autoSnakeCaseNames != false, autoIdAndDateFields != false);
var ctx = new PostgresBuildContext(raw, annotation, resolver, buildStep,
tableName: annotation.tableName?.isNotEmpty == true
? annotation.tableName
: pluralize(new ReCase(clazz.name).snakeCase),
autoSnakeCaseNames: autoSnakeCaseNames != false,
autoIdAndDateFields: autoIdAndDateFields != false);
var relations = new TypeChecker.fromRuntime(Relationship);
List<String> fieldNames = [];
List<FieldElement> fields = [];
for (var field in raw.fields) {
fieldNames.add(field.name);
// Check for relationship. If so, skip.
var relationshipAnnotation = relations.firstAnnotationOf(field);
/* findAnnotation<HasOne>(field, HasOne) ??
findAnnotation<HasMany>(field, HasMany) ??
findAnnotation<BelongsTo>(field, BelongsTo);*/
if (relationshipAnnotation != null) {
int type = -1;
switch (relationshipAnnotation.type.name) {
case 'HasMany':
type = RelationshipType.HAS_MANY;
break;
case 'HasOne':
type = RelationshipType.HAS_ONE;
break;
case 'BelongsTo':
type = RelationshipType.BELONGS_TO;
break;
case 'BelongsToMany':
type = RelationshipType.BELONGS_TO_MANY;
break;
default:
throw new UnsupportedError(
'Unsupported relationship type "${relationshipAnnotation.type.name}".');
}
ctx.relationshipFields.add(field);
ctx.relationships[field.name] = new Relationship(type,
localKey:
relationshipAnnotation.getField('localKey')?.toStringValue(),
foreignKey:
relationshipAnnotation.getField('foreignKey')?.toStringValue(),
foreignTable:
relationshipAnnotation.getField('foreignTable')?.toStringValue(),
cascadeOnDelete: relationshipAnnotation
.getField('cascadeOnDelete')
?.toBoolValue());
continue;
}
// Check for column annotation...
var column = findAnnotation<Column>(field, Column);
if (column == null && field.name == 'id' && ctx.shimmed['id'] == true) {
column = const Column(type: ColumnType.SERIAL);
}
if (column == null) {
// Guess what kind of column this is...
switch (field.type.name) {
case 'String':
column = const Column(type: ColumnType.VAR_CHAR);
break;
case 'int':
column = const Column(type: ColumnType.INT);
break;
case 'double':
column = const Column(type: ColumnType.DECIMAL);
break;
case 'num':
column = const Column(type: ColumnType.NUMERIC);
break;
case 'num':
column = const Column(type: ColumnType.NUMERIC);
break;
case 'bool':
column = const Column(type: ColumnType.BOOLEAN);
break;
case 'DateTime':
column = const Column(type: ColumnType.TIME_STAMP);
break;
}
}
if (column == null)
throw 'Cannot infer SQL column type for field "${field.name}" with type "${field.type.name}".';
ctx.columnInfo[field.name] = column;
fields.add(field);
}
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);
}

View file

@ -1,143 +0,0 @@
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';
import 'postgres_build_context.dart';
class SQLMigrationGenerator implements Builder {
/// If `true` (default), then field names will automatically be (de)serialized as snake_case.
final bool autoSnakeCaseNames;
/// If `true` (default), then the schema will automatically add id, created_at and updated_at fields.
final bool autoIdAndDateFields;
/// If `true` (default: `false`), then the resulting schema will generate a `TEMPORARY` table.
final bool temporary;
const SQLMigrationGenerator(
{this.autoSnakeCaseNames: true,
this.autoIdAndDateFields: true,
this.temporary: false});
@override
Map<String, List<String>> get buildExtensions => {
'.dart': ['.up.g.sql', '.down.g.sql']
};
@override
Future build(BuildStep buildStep) async {
var resolver = await buildStep.resolver;
var up = new StringBuffer();
var down = new StringBuffer();
if (!await resolver.isLibrary(buildStep.inputId)) {
return;
}
var lib = await resolver.getLibrary(buildStep.inputId);
var elements = getElementsFromLibraryElement(lib);
if (!elements.any(
(el) => el.metadata.any((ann) => matchAnnotation(ORM, ann)))) return;
generateSqlMigrations(lib, resolver, buildStep, up, down);
buildStep.writeAsString(
buildStep.inputId.changeExtension('.up.g.sql'), up.toString());
buildStep.writeAsString(
buildStep.inputId.changeExtension('.down.g.sql'), down.toString());
}
void generateSqlMigrations(LibraryElement libraryElement, Resolver resolver,
BuildStep buildStep, StringBuffer up, StringBuffer down) {
List<String> done = [];
for (var element in getElementsFromLibraryElement(libraryElement)) {
if (element is ClassElement && !done.contains(element.name)) {
var ann = element.metadata
.firstWhere((a) => matchAnnotation(ORM, a), orElse: () => null);
if (ann != null) {
var ctx = buildContext(
element,
instantiateAnnotation(ann),
buildStep,
resolver,
autoSnakeCaseNames != false,
autoIdAndDateFields != false);
buildUpMigration(ctx, up);
buildDownMigration(ctx, down);
done.add(element.name);
}
}
}
}
void buildUpMigration(PostgresBuildContext ctx, StringBuffer buf) {
if (temporary == true)
buf.writeln('CREATE TEMPORARY TABLE "${ctx.tableName}" (');
else
buf.writeln('CREATE TABLE "${ctx.tableName}" (');
int i = 0;
ctx.columnInfo.forEach((name, col) {
if (i++ > 0) buf.writeln(',');
var key = ctx.resolveFieldName(name);
buf.write(' "$key" ${col.type.name}');
if (col.index == IndexType.PRIMARY_KEY)
buf.write(' PRIMARY KEY');
else if (col.index == IndexType.UNIQUE) buf.write(' UNIQUE');
if (col.nullable != true) buf.write(' NOT NULLABLE');
});
// Relations
ctx.relationships.forEach((name, r) {
var relationship = ctx.populateRelationship(name);
if (relationship.isBelongsTo) {
if (i++ > 0) buf.writeln(',');
buf.write(
' "${relationship.localKey}" int REFERENCES ${relationship.foreignTable}(${relationship.foreignKey})');
if (relationship.cascadeOnDelete != false && relationship.isSingular)
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(');');
}
void buildDownMigration(PostgresBuildContext ctx, StringBuffer buf) {
buf.writeln('DROP TABLE "${ctx.tableName}";');
}
}

View file

@ -1,939 +0,0 @@
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';
import 'package:source_gen/src/utils.dart';
import 'package:source_gen/source_gen.dart';
import 'build_context.dart';
import 'postgres_build_context.dart';
const List<String> RELATIONS = const ['or'];
const List<String> RESTRICTORS = const ['limit', 'offset'];
const Map<String, String> SORT_MODES = const {
'Descending': 'DESC',
'Ascending': 'ASC'
};
// TODO: HasOne, HasMany
class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
/// If "true" (default), then field names will automatically be (de)serialized as snake_case.
final bool autoSnakeCaseNames;
/// If "true" (default), then
final bool autoIdAndDateFields;
const PostgresORMGenerator(
{this.autoSnakeCaseNames: true, this.autoIdAndDateFields: true});
@override
Future<String> generateForAnnotatedElement(
Element element, ORM annotation, BuildStep buildStep) async {
if (buildStep.inputId.path.contains('.orm.g.dart')) {
return null;
}
if (element is! ClassElement)
throw 'Only classes can be annotated with @ORM().';
var resolver = await buildStep.resolver;
var lib =
generateOrmLibrary(element.library, resolver, buildStep).buildAst();
if (lib == null) return null;
return prettyToSource(lib);
}
LibraryBuilder generateOrmLibrary(
LibraryElement libraryElement, Resolver resolver, BuildStep buildStep) {
var lib = new LibraryBuilder();
lib.addDirective(new ImportBuilder('dart:async'));
lib.addDirective(new ImportBuilder('package:angel_orm/angel_orm.dart'));
lib.addDirective(new ImportBuilder('package:postgres/postgres.dart'));
lib.addDirective(new ImportBuilder(p.basename(buildStep.inputId.path)));
var elements = getElementsFromLibraryElement(libraryElement)
.where((el) => el is ClassElement);
Map<ClassElement, PostgresBuildContext> contexts = {};
List<String> done = [];
List<String> imported = [];
for (var element in elements) {
if (!done.contains(element.name)) {
var ann = element.metadata
.firstWhere((a) => matchAnnotation(ORM, a), orElse: () => null);
if (ann != null) {
var ctx = contexts[element] = buildContext(
element,
instantiateAnnotation(ann),
buildStep,
resolver,
autoSnakeCaseNames != false,
autoIdAndDateFields != false);
ctx.relationships.forEach((name, r) {
var relationship = ctx.populateRelationship(name);
var field = ctx.resolveRelationshipField(name);
var uri = field.type.element.source.uri;
var pathName = p
.basenameWithoutExtension(p.basenameWithoutExtension(uri.path));
var source =
'$pathName.orm.g.dart'; //uri.resolve('$pathName.orm.g.dart').toString();
// TODO: Find good way to source url...
source = new ReCase(relationship.isList
? relationship.modelType.name
: field.type.name)
.snakeCase +
'.orm.g.dart';
if (!imported.contains(source)) {
lib.addDirective(new ImportBuilder(source));
imported.add(source);
}
});
}
}
}
if (contexts.isEmpty) return null;
done.clear();
for (var element in contexts.keys) {
if (!done.contains(element.name)) {
var ctx = contexts[element];
lib.addMember(buildQueryClass(ctx));
lib.addMember(buildWhereClass(ctx));
done.add(element.name);
}
}
return lib;
}
ClassBuilder buildQueryClass(PostgresBuildContext ctx) {
var clazz = new ClassBuilder(ctx.queryClassName);
// Add constructor + field
var connection = reference('connection');
// Add _unions
clazz.addField(varFinal('_unions',
value: map({}),
type: new TypeBuilder('Map',
genericTypes: [ctx.queryClassBuilder, lib$core.bool])));
var unions = <String, bool>{'union': false, 'unionAll': true};
unions.forEach((name, all) {
var meth = new MethodBuilder(name, returnType: lib$core.$void);
meth.addPositional(parameter('query', [ctx.queryClassBuilder]));
meth.addStatement(
literal(all).asAssign(reference('_unions')[reference('query')]));
clazz.addMethod(meth);
});
// Add _sortMode
clazz.addField(varField('_sortKey', type: lib$core.String));
clazz.addField(varField('_sortMode', type: lib$core.String));
SORT_MODES.keys.forEach((sort) {
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((literal(ctx.prefix) + reference('key'))
.parentheses()
.asAssign(reference('_sortKey')));
clazz.addMethod(m);
});
// Add limit, offset
for (var restrictor in RESTRICTORS) {
clazz.addField(varField(restrictor, type: lib$core.int));
}
// Add and, or, not
for (var relation in RELATIONS) {
clazz.addField(varFinal('_$relation',
type: new TypeBuilder('List', genericTypes: [ctx.whereClassBuilder]),
value: list([])));
var relationMethod =
new MethodBuilder(relation, returnType: lib$core.$void);
relationMethod
.addPositional(parameter('selector', [ctx.whereClassBuilder]));
relationMethod.addStatement(
reference('_$relation').invoke('add', [reference('selector')]));
clazz.addMethod(relationMethod);
}
// Add _buildSelectQuery()
// Add where...
clazz.addField(varFinal('where',
type: new TypeBuilder(ctx.whereClassName),
value: new TypeBuilder(ctx.whereClassName).newInstance([])));
// Add toSql()...
clazz.addMethod(buildToSqlMethod(ctx));
// Add parseRow()...
clazz.addMethod(buildParseRowMethod(ctx), asStatic: true);
// Add get()...
clazz.addMethod(buildGetMethod(ctx));
// Add getOne()...
clazz.addMethod(buildGetOneMethod(ctx), asStatic: true);
// Add update()...
clazz.addMethod(buildUpdateMethod(ctx));
// Add delete()...
clazz.addMethod(buildDeleteMethod(ctx));
// Add deleteOne()...
clazz.addMethod(buildDeleteOneMethod(ctx), asStatic: true);
// Add insert()...
clazz.addMethod(buildInsertMethod(ctx), asStatic: true);
// Add insertX()
clazz.addMethod(buildInsertModelMethod(ctx), asStatic: true);
// Add updateX()
clazz.addMethod(buildUpdateModelMethod(ctx), asStatic: true);
// Add getAll() => new TodoQuery().get();
clazz.addMethod(
new MethodBuilder('getAll',
returnType: new TypeBuilder('Stream',
genericTypes: [ctx.modelClassBuilder]),
returns: ctx.queryClassBuilder
.newInstance([]).invoke('get', [connection]))
..addPositional(
parameter('connection', [ctx.postgreSQLConnectionBuilder])),
asStatic: true);
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) {
var meth = new MethodBuilder('toSql', returnType: lib$core.String);
meth.addPositional(parameter('prefix', [lib$core.String]).asOptional());
var buf = reference('buf');
meth.addStatement(
varField('buf', value: lib$core.StringBuffer.newInstance([])));
// Write prefix, or default to SELECT
var prefix = reference('prefix');
meth.addStatement(buf.invoke('write', [
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: Belongs to many, has many
if (relationship.isSingular) {
String b = ' LEFT OUTER JOIN ${relationship.foreignTable} ON ${ctx.tableName}.${relationship.localKey} = ${relationship.foreignTable}.${relationship.foreignKey}';
relationsIfThen.addStatement(buf.invoke('write', [literal(b)]));
}
});
meth.addStatement(relationsIfThen);
meth.addStatement(varField('whereClause',
value: reference('where').invoke('toWhereClause', [])));
var whereClause = reference('whereClause');
meth.addStatement(ifThen(whereClause.notEquals(literal(null)), [
buf.invoke('write', [literal(' ') + whereClause])
]));
for (var relation in RELATIONS) {
var ref = reference('_$relation'),
x = reference('x'),
whereClause = reference('whereClause');
var upper = relation.toUpperCase();
var closure = new MethodBuilder.closure();
closure.addPositional(parameter('x'));
closure.addStatement(varField('whereClause',
value: x.invoke('toWhereClause', [],
namedArguments: {'keyword': literal(false)})));
closure.addStatement(ifThen(whereClause.notEquals(literal(null)), [
buf.invoke('write', [literal(' $upper (') + whereClause + literal(')')])
]));
meth.addStatement(ref.invoke('forEach', [closure]));
}
var ifNoPrefix = ifThen(reference('prefix').equals(literal(null)));
for (var restrictor in RESTRICTORS) {
var ref = reference(restrictor);
var upper = restrictor.toUpperCase();
ifNoPrefix.addStatement(ifThen(ref.notEquals(literal(null)), [
buf.invoke('write', [literal(' $upper ') + ref.invoke('toString', [])])
]));
}
var sortMode = reference('_sortMode');
SORT_MODES.forEach((k, sort) {
ifNoPrefix.addStatement(ifThen(sortMode.equals(literal(k)), [
buf.invoke('write', [
literal(' ORDER BY "') + reference('_sortKey') + literal('" $sort')
])
]));
});
// Add unions
var unionClosure = new MethodBuilder.closure();
unionClosure.addPositional(parameter('query'));
unionClosure.addPositional(parameter('all'));
unionClosure.addStatement(buf.invoke('write', [literal(' UNION')]));
unionClosure.addStatement(ifThen(reference('all'), [
buf.invoke('write', [literal(' ALL')])
]));
unionClosure.addStatement(buf.invoke('write', [literal(' (')]));
unionClosure.addStatement(varField('sql',
value: reference('query').invoke('toSql', []).invoke(
'replaceAll', [literal(';'), literal('')])));
unionClosure
.addStatement(buf.invoke('write', [reference('sql') + literal(')')]));
ifNoPrefix
.addStatement(reference('_unions').invoke('forEach', [unionClosure]));
ifNoPrefix.addStatement(buf.invoke('write', [literal(';')]));
meth.addStatement(ifNoPrefix);
meth.addStatement(buf.invoke('toString', []).asReturn());
return meth;
}
MethodBuilder buildParseRowMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('parseRow', returnType: ctx.modelClassBuilder);
meth.addPositional(parameter('row', [lib$core.List]));
//meth.addStatement(lib$core.print.call(
// [literal('ROW MAP: ') + reference('row').invoke('toString', [])]));
var row = reference('row');
// We want to create a Map using the SQL row.
Map<String, ExpressionBuilder> data = {};
int i = 0;
ctx.fields.forEach((field) {
var name = ctx.resolveFieldName(field.name);
var rowKey = row[literal(i++)];
if (field.name == 'id' && ctx.shimmed.containsKey('id')) {
data[name] = rowKey.invoke('toString', []);
} else
data[name] = 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.isList
? relationship.modelType.name
: relationship.dartType.name);
var relationshipQuery = new TypeBuilder('${rc.pascalCase}Query');
List<ExpressionBuilder> 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(result.asReturn());
return 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(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]));
meth.addStatement(ctrl.property('stream').asReturn());
}
MethodBuilder buildGetMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('get',
returnType:
new TypeBuilder('Stream', genericTypes: [ctx.modelClassBuilder]));
meth.addPositional(
parameter('connection', [ctx.postgreSQLConnectionBuilder]));
var streamController = new TypeBuilder('StreamController',
genericTypes: [ctx.modelClassBuilder]);
meth.addStatement(varField('ctrl',
type: streamController, value: streamController.newInstance([])));
var future =
reference('connection').invoke('query', [reference('toSql').call([])]);
_invokeStreamClosure(ctx, future, meth);
return meth;
}
MethodBuilder buildGetOneMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('getOne',
returnType:
new TypeBuilder('Future', genericTypes: [ctx.modelClassBuilder]));
meth.addPositional(parameter('id', [lib$core.int]));
meth.addPositional(
parameter('connection', [ctx.postgreSQLConnectionBuilder]));
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;
}
void _addAllNamed(MethodBuilder meth, PostgresBuildContext ctx) {
// Add all named params
ctx.fields.forEach((field) {
if (field.name != 'id') {
var p = new ParameterBuilder(field.name,
type: new TypeBuilder(field.type.name));
var column = ctx.columnInfo[field.name];
if (column?.defaultValue != null)
p = p.asOptional(literal(column.defaultValue));
meth.addNamed(p);
}
});
}
void _addReturning(StringBuffer buf, PostgresBuildContext ctx) {
buf.write(' RETURNING ');
int i = 0;
ctx.fields.forEach((field) {
if (i++ > 0) buf.write(', ');
var name = ctx.resolveFieldName(field.name);
buf.write('"$name"');
});
buf.write(';');
}
void _ensureDates(MethodBuilder meth, PostgresBuildContext ctx) {
if (ctx.fields.any((f) => f.name == 'createdAt' || f.name == 'updatedAt')) {
meth.addStatement(varField('__ormNow__',
value: lib$core.DateTime.newInstance([], constructor: 'now')));
}
}
Map<String, ExpressionBuilder> _buildSubstitutionValues(
PostgresBuildContext ctx) {
Map<String, ExpressionBuilder> substitutionValues = {};
ctx.fields.forEach((field) {
if (field.name == 'id')
return;
else if (field.name == 'createdAt' || field.name == 'updatedAt') {
var ref = reference(field.name);
substitutionValues[field.name] =
ref.notEquals(literal(null)).ternary(ref, reference('__ormNow__'));
} else
substitutionValues[field.name] = reference(field.name);
});
return substitutionValues;
}
ExpressionBuilder _executeQuery(ExpressionBuilder queryString,
MethodBuilder meth, Map<String, ExpressionBuilder> substitutionValues) {
var connection = reference('connection');
var query = queryString;
return connection.invoke('query', [query],
namedArguments: {'substitutionValues': map(substitutionValues)});
}
MethodBuilder buildUpdateMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('update',
returnType:
new TypeBuilder('Stream', genericTypes: [ctx.modelClassBuilder]));
meth.addPositional(
parameter('connection', [ctx.postgreSQLConnectionBuilder]));
_addAllNamed(meth, ctx);
var buf = new StringBuffer('UPDATE "${ctx.tableName}" SET (');
int i = 0;
ctx.fields.forEach((field) {
if (field.name == 'id')
return;
else {
if (i++ > 0) buf.write(', ');
var key = ctx.resolveFieldName(field.name);
buf.write('"$key"');
}
});
buf.write(') = (');
i = 0;
ctx.fields.forEach((field) {
if (field.name == 'id')
return;
else {
if (i++ > 0) buf.write(', ');
buf.write('@${field.name}');
}
});
buf.write(') ');
var $buf = reference('buf');
var whereClause = reference('whereClause');
meth.addStatement(varField('buf',
value: lib$core.StringBuffer.newInstance([literal(buf.toString())])));
meth.addStatement(varField('whereClause',
value: reference('where').invoke('toWhereClause', [])));
meth.addStatement(ifThen(whereClause.notEquals(literal(null)), [
$buf.invoke('write', [whereClause])
]));
var buf2 = new StringBuffer();
_addReturning(buf2, ctx);
_ensureDates(meth, ctx);
var substitutionValues = _buildSubstitutionValues(ctx);
var ctrlType = new TypeBuilder('StreamController',
genericTypes: [ctx.modelClassBuilder]);
meth.addStatement(varField('ctrl', value: ctrlType.newInstance([])));
var result = _executeQuery(
$buf.invoke('toString', []) + literal(buf2.toString()),
meth,
substitutionValues);
_invokeStreamClosure(ctx, result, meth);
return meth;
}
MethodBuilder buildDeleteMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('delete',
returnType:
new TypeBuilder('Stream', genericTypes: [ctx.modelClassBuilder]));
meth.addPositional(
parameter('connection', [ctx.postgreSQLConnectionBuilder]));
var litBuf = new StringBuffer();
_addReturning(litBuf, ctx);
var streamController = new TypeBuilder('StreamController',
genericTypes: [ctx.modelClassBuilder]);
meth.addStatement(varField('ctrl',
type: streamController, value: streamController.newInstance([])));
var future = reference('connection').invoke('query', [
reference('toSql').call([literal('DELETE FROM "${ctx.tableName}"')]) +
literal(litBuf.toString())
]);
_invokeStreamClosure(ctx, future, meth);
return meth;
}
MethodBuilder buildDeleteOneMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('deleteOne',
returnType:
new TypeBuilder('Future', genericTypes: [ctx.modelClassBuilder]))
..addPositional(parameter('id', [lib$core.int]))
..addPositional(
parameter('connection', [ctx.postgreSQLConnectionBuilder]));
var id = reference('id'),
connection = reference('connection'),
query = reference('query');
meth.addStatement(
varField('query', value: ctx.queryClassBuilder.newInstance([])));
meth.addStatement(
query.property('where').property('id').invoke('equals', [id]));
meth.addStatement(
query.invoke('delete', [connection]).property('first').asReturn());
return meth;
}
MethodBuilder buildInsertMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('insert',
modifier: MethodModifier.asAsync,
returnType:
new TypeBuilder('Future', genericTypes: [ctx.modelClassBuilder]));
meth.addPositional(
parameter('connection', [ctx.postgreSQLConnectionBuilder]));
// Add all named params
_addAllNamed(meth, ctx);
var buf = new StringBuffer('INSERT INTO "${ctx.tableName}" (');
int i = 0;
ctx.fields.forEach((field) {
if (field.name == 'id')
return;
else {
if (i++ > 0) buf.write(', ');
var key = ctx.resolveFieldName(field.name);
buf.write('"$key"');
}
});
buf.write(') VALUES (');
i = 0;
ctx.fields.forEach((field) {
if (field.name == 'id')
return;
else {
if (i++ > 0) buf.write(', ');
buf.write('@${field.name}');
}
});
buf.write(')');
// meth.addStatement(lib$core.print.call([literal(buf.toString())]));
_addReturning(buf, ctx);
_ensureDates(meth, ctx);
var substitutionValues = _buildSubstitutionValues(ctx);
var connection = reference('connection');
var query = literal(buf.toString());
var result = reference('result'), output = reference('output');
meth.addStatement(varField('result',
value: connection.invoke('query', [
query
], namedArguments: {
'substitutionValues': map(substitutionValues)
}).asAwait()));
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
ctx.relationships.forEach((name, r) {
var relationship = ctx.populateRelationship(name);
var rc = new ReCase(relationship.isList
? relationship.modelType.name
: 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) {
matched = ctx.resolveRelationshipField(name) != null;
}
if (!matched)
throw 'Couldn\'t resolve row index for relationship "${name}".';
var idAsInt = row[literal(col)];
if (relationship.isSingular) {
if (relationship.isBelongsTo) {
meth.addStatement(type
.invoke('getOne', [idAsInt, reference('connection')])
.asAwait()
.asAssign(output.property(name)));
} else {
var query = reference('${rc.camelCase}Query');
meth.addStatement(
varField('${rc.camelCase}Query', value: type.newInstance([])));
// Set id to row[0]
meth.addStatement(query
.property('where')
.property('id')
.invoke('equals', [row[literal(0)]]));
var fetched = query
.invoke('get', [reference('connection')])
.property('first')
.invoke('catchError', [
new MethodBuilder.closure(returns: literal(null))
..addPositional(parameter('_'))
])
.asAwait();
meth.addStatement(fetched.asAssign(output.property(name)));
}
} else {
var query = reference('${rc.camelCase}Query');
meth.addStatement(
varField('${rc.camelCase}Query', value: type.newInstance([])));
ExpressionBuilder fetched;
// TODO: HasMany
if (relationship.isBelongsTo) {
meth.addStatement(query
.property('where')
.property('id')
.invoke('equals', [idAsInt]));
fetched = query.invoke('get', [reference('connection')]).invoke(
'toList', []).asAwait();
}
meth.addStatement(output.property(name).invoke('addAll', [fetched]));
}
});
}
void _addRelationshipConstraintsNamed(
MethodBuilder m, PostgresBuildContext ctx) {
ctx.relationships.forEach((name, r) {
var relationship = ctx.populateRelationship(name);
if (relationship.isBelongsTo) {
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}',
returnType:
new TypeBuilder('Future', genericTypes: [ctx.modelClassBuilder]));
meth.addPositional(
parameter('connection', [ctx.postgreSQLConnectionBuilder]));
meth.addPositional(parameter(rc.camelCase, [ctx.modelClassBuilder]));
_addRelationshipConstraintsNamed(meth, ctx);
Map<String, ExpressionBuilder> args = {};
var ref = reference(rc.camelCase);
ctx.fields.forEach((f) {
if (f.name != 'id') {
args[f.name] = f is RelationshipConstraintField
? reference(f.name)
: ref.property(f.name);
}
});
meth.addStatement(ctx.queryClassBuilder
.invoke('insert', [reference('connection')], namedArguments: args)
.asReturn());
return meth;
}
MethodBuilder buildUpdateModelMethod(PostgresBuildContext ctx) {
var rc = new ReCase(ctx.modelClassName);
var meth = new MethodBuilder('update${rc.pascalCase}',
returnType:
new TypeBuilder('Future', genericTypes: [ctx.modelClassBuilder]));
meth.addPositional(
parameter('connection', [ctx.postgreSQLConnectionBuilder]));
meth.addPositional(parameter(rc.camelCase, [ctx.modelClassBuilder]));
// var query = new XQuery();
var ref = reference(rc.camelCase);
var query = reference('query');
meth.addStatement(
varField('query', value: ctx.queryClassBuilder.newInstance([])));
// query.where.id.equals(x.id);
meth.addStatement(query.property('where').property('id').invoke('equals', [
lib$core.int.invoke('parse', [ref.property('id')])
]));
// return query.update(connection, ...).first;
Map<String, ExpressionBuilder> args = {};
ctx.fields.forEach((f) {
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 =
query.invoke('update', [reference('connection')], namedArguments: args);
meth.addStatement(update.property('first').asReturn());
return meth;
}
ClassBuilder buildWhereClass(PostgresBuildContext ctx) {
var clazz = new ClassBuilder(ctx.whereClassName);
ctx.fields.forEach((field) {
TypeBuilder queryBuilderType;
List<ExpressionBuilder> args = [];
if (field.name == 'id') {
queryBuilderType = new TypeBuilder('NumericSqlExpressionBuilder',
genericTypes: [lib$core.int]);
} else {
switch (field.type.name) {
case 'String':
queryBuilderType = new TypeBuilder('StringSqlExpressionBuilder');
break;
case 'int':
queryBuilderType = new TypeBuilder('NumericSqlExpressionBuilder',
genericTypes: [lib$core.int]);
break;
case 'double':
queryBuilderType = new TypeBuilder('NumericSqlExpressionBuilder',
genericTypes: [new TypeBuilder('double')]);
break;
case 'num':
queryBuilderType = new TypeBuilder('NumericSqlExpressionBuilder');
break;
case 'bool':
queryBuilderType = new TypeBuilder('BooleanSqlExpressionBuilder');
break;
case 'DateTime':
queryBuilderType = new TypeBuilder('DateTimeSqlExpressionBuilder');
args.add(literal(
ctx.tableName + '.' + ctx.resolveFieldName(field.name)));
break;
}
}
if (queryBuilderType == null)
throw 'Could not resolve query builder type for field "${field.name}" of type "${field.type.name}".';
clazz.addField(varFinal(field.name,
type: queryBuilderType, value: queryBuilderType.newInstance(args)));
});
// Create "toWhereClause()"
var toWhereClause =
new MethodBuilder('toWhereClause', returnType: lib$core.String);
toWhereClause.addNamed(parameter('keyword', [lib$core.bool]));
// List<String> expressions = [];
toWhereClause.addStatement(varFinal('expressions',
type: new TypeBuilder('List', genericTypes: [lib$core.String]),
value: list([])));
var expressions = reference('expressions');
// Add all expressions...
ctx.fields.forEach((field) {
var name = ctx.resolveFieldName(field.name);
var queryBuilder = reference(field.name);
var toAdd = field.type.isAssignableTo(ctx.dateTimeType)
? queryBuilder.invoke('compile', [])
: (literal('${ctx.tableName}.$name ') +
queryBuilder.invoke('compile', []));
toWhereClause.addStatement(ifThen(queryBuilder.property('hasValue'), [
expressions.invoke('add', [toAdd])
]));
});
var kw = reference('keyword')
.notEquals(literal(false))
.ternary(literal('WHERE '), literal(''))
.parentheses();
// return expressions.isEmpty ? null : ('WHERE ' + expressions.join(' AND '));
toWhereClause.addStatement(expressions
.property('isEmpty')
.ternary(literal(null),
(kw + expressions.invoke('join', [literal(' AND ')])).parentheses())
.asReturn());
clazz.addMethod(toWhereClause);
return clazz;
}
}

View file

@ -1,259 +0,0 @@
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';
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;
LibraryElement _libraryCache;
TypeProvider _typeProviderCache;
TypeBuilder _modelClassBuilder,
_queryClassBuilder,
_whereClassBuilder,
_postgresqlConnectionBuilder;
String _prefix;
final Map<String, Relationship> _populatedRelationships = {};
final Map<String, Column> columnInfo = {};
final Map<String, IndexType> indices = {};
final Map<String, Relationship> relationships = {};
final bool autoSnakeCaseNames, autoIdAndDateFields;
final String tableName;
final ORM ormAnnotation;
final BuildContext raw;
final Resolver resolver;
final BuildStep buildStep;
String primaryKeyName = 'id';
PostgresBuildContext(
this.raw, this.ormAnnotation, this.resolver, this.buildStep,
{this.tableName, this.autoSnakeCaseNames, this.autoIdAndDateFields})
: super(raw.annotation,
originalClassName: raw.originalClassName,
sourceFilename: raw.sourceFilename);
final List<FieldElement> fields = [], relationshipFields = [];
TypeBuilder get modelClassBuilder =>
_modelClassBuilder ??= new TypeBuilder(modelClassName);
TypeBuilder get queryClassBuilder =>
_queryClassBuilder ??= new TypeBuilder(queryClassName);
TypeBuilder get whereClassBuilder =>
_whereClassBuilder ??= new TypeBuilder(whereClassName);
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<String, String> get aliases => raw.aliases;
Map<String, bool> get shimmed => raw.shimmed;
String get sourceFilename => raw.sourceFilename;
String get modelClassName => raw.modelClassName;
String get originalClassName => raw.originalClassName;
String get queryClassName => modelClassName + 'Query';
String get whereClassName => queryClassName + 'Where';
LibraryElement get library =>
_libraryCache ??= resolver.getLibrary(buildStep.inputId);
DartType get dateTimeType => _dateTimeTypeCache ??= (resolver.libraries
.firstWhere((lib) => lib.isDartCore)
.getType('DateTime')
.type);
TypeProvider get typeProvider =>
_typeProviderCache ??= library.context.typeProvider;
FieldElement resolveRelationshipField(String name) =>
relationshipFields.firstWhere((f) => f.name == name, orElse: () => null);
PopulatedRelationship populateRelationship(String name) {
return _populatedRelationships.putIfAbsent(name, () {
var f = raw.fields.firstWhere((f) => f.name == name);
var relationship = relationships[name];
DartType refType = f.type;
if (refType.isAssignableTo(typeProvider.listType) || refType.name == 'List') {
var iType = refType as InterfaceType;
if (iType.typeArguments.isEmpty)
throw 'Relationship "${f.name}" cannot be modeled as a generic List.';
refType = iType.typeArguments.first;
}
var typeName =
refType.name.startsWith('_') ? refType.name.substring(1) : refType.name;
var rc = new ReCase(typeName);
if (relationship.type == RelationshipType.HAS_ONE ||
relationship.type == RelationshipType.HAS_MANY) {
var single = singularize(tableName);
var foreignKey = relationship.foreignTable ??
(autoSnakeCaseNames != false
? '${single}_id'
: '${single}Id');
var localKey = relationship.localKey ?? 'id';
var foreignTable = relationship.foreignTable ??
(autoSnakeCaseNames != false
? pluralize(rc.snakeCase)
: pluralize(typeName));
return new PopulatedRelationship(
relationship.type,
f.name,
f.type,
buildStep,
resolver,
autoSnakeCaseNames,
autoIdAndDateFields,
relationship.type == RelationshipType.HAS_ONE,
typeProvider,
localKey: localKey,
foreignKey: foreignKey,
foreignTable: foreignTable,
cascadeOnDelete: relationship.cascadeOnDelete);
} else if (relationship.type == RelationshipType.BELONGS_TO ||
relationship.type == RelationshipType.BELONGS_TO_MANY) {
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.name,
f.type,
buildStep,
resolver,
autoSnakeCaseNames,
autoIdAndDateFields,
relationship.type == RelationshipType.BELONGS_TO,
typeProvider,
localKey: localKey,
foreignKey: foreignKey,
foreignTable: foreignTable,
cascadeOnDelete: relationship.cascadeOnDelete);
} else
throw new UnsupportedError(
'Invalid relationship type: ${relationship.type}');
});
}
}
class PopulatedRelationship extends Relationship {
bool _isList;
DartType _modelType;
PostgresBuildContext _modelTypeContext;
DartObject _modelTypeORM;
final String originalName;
final DartType dartType;
final BuildStep buildStep;
final Resolver resolver;
final bool autoSnakeCaseNames, autoIdAndDateFields;
final bool isSingular;
final TypeProvider typeProvider;
PopulatedRelationship(
int type,
this.originalName,
this.dartType,
this.buildStep,
this.resolver,
this.autoSnakeCaseNames,
this.autoIdAndDateFields,
this.isSingular,
this.typeProvider,
{String localKey,
String foreignKey,
String foreignTable,
bool cascadeOnDelete})
: super(type,
localKey: localKey,
foreignKey: foreignKey,
foreignTable: foreignTable,
cascadeOnDelete: cascadeOnDelete);
bool get isBelongsTo =>
type == RelationshipType.BELONGS_TO ||
type == RelationshipType.BELONGS_TO_MANY;
bool get isHas =>
type == RelationshipType.HAS_ONE || type == RelationshipType.HAS_MANY;
bool get isList => _isList ??=
dartType.isAssignableTo(typeProvider.listType) || dartType.name == 'List';
DartType get modelType {
if (_modelType != null) return _modelType;
DartType searchType = dartType;
var ormChecker = new TypeChecker.fromRuntime(ORM);
// Get inner type from List if any...
if (!isSingular) {
if (!isList)
throw '"$originalName" is a many-to-one relationship, and thus it should be represented as a List within your Dart class. You have it represented as ${dartType.name}.';
else {
var iType = dartType as InterfaceType;
if (iType.typeArguments.isEmpty)
throw '"$originalName" is a many-to-one relationship, and should be modeled as a List that references another model type. Example: `List<T>`, where T is a model type.';
else
searchType = iType.typeArguments.first;
}
}
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);
}
}

View file

@ -33,5 +33,10 @@ final PhaseGroup PHASES = new PhaseGroup()
new GeneratorBuilder([new PostgresORMGenerator()], new GeneratorBuilder([new PostgresORMGenerator()],
isStandalone: true, generatedExtension: '.orm.g.dart'), isStandalone: true, generatedExtension: '.orm.g.dart'),
DEPENDENT_MODELS)) DEPENDENT_MODELS))
..addPhase(new Phase()
..addAction(
new GeneratorBuilder([new PostgresServiceGenerator()],
isStandalone: true, generatedExtension: '.service.g.dart'),
ALL_MODELS))
..addPhase(new Phase() ..addPhase(new Phase()
..addAction(new SQLMigrationGenerator(temporary: true), ALL_MODELS)); ..addAction(new SQLMigrationGenerator(temporary: true), ALL_MODELS));