From abf438196e667db04a8cb2a94c7b091b423fa61c Mon Sep 17 00:00:00 2001 From: thosakwe Date: Thu, 3 Aug 2017 14:42:01 -0400 Subject: [PATCH] Start service gen --- .../lib/angel_orm_generator.dart | 5 +- .../lib/src/builder/orm/service.dart | 178 ++++ .../src/builder/postgres/build_context.dart | 141 --- .../lib/src/builder/postgres/migration.dart | 143 --- .../lib/src/builder/postgres/postgres.dart | 939 ------------------ .../postgres/postgres_build_context.dart | 259 ----- angel_orm_generator/tool/phases.dart | 5 + 7 files changed, 186 insertions(+), 1484 deletions(-) create mode 100644 angel_orm_generator/lib/src/builder/orm/service.dart delete mode 100644 angel_orm_generator/lib/src/builder/postgres/build_context.dart delete mode 100644 angel_orm_generator/lib/src/builder/postgres/migration.dart delete mode 100644 angel_orm_generator/lib/src/builder/postgres/postgres.dart delete mode 100644 angel_orm_generator/lib/src/builder/postgres/postgres_build_context.dart diff --git a/angel_orm_generator/lib/angel_orm_generator.dart b/angel_orm_generator/lib/angel_orm_generator.dart index be464374..34fa9f8e 100644 --- a/angel_orm_generator/lib/angel_orm_generator.dart +++ b/angel_orm_generator/lib/angel_orm_generator.dart @@ -1,2 +1,3 @@ -export 'src/builder/postgres/migration.dart'; -export 'src/builder/postgres/postgres.dart'; \ No newline at end of file +export 'src/builder/orm/migration.dart'; +export 'src/builder/orm/postgres.dart'; +export 'src/builder/orm/service.dart'; \ No newline at end of file diff --git a/angel_orm_generator/lib/src/builder/orm/service.dart b/angel_orm_generator/lib/src/builder/orm/service.dart new file mode 100644 index 00000000..ea672f12 --- /dev/null +++ b/angel_orm_generator/lib/src/builder/orm/service.dart @@ -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 { + final bool autoSnakeCaseNames; + + final bool autoIdAndDateFields; + + const PostgresServiceGenerator( + {this.autoSnakeCaseNames: true, this.autoIdAndDateFields: true}); + + @override + Future 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 contexts = {}; + List 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> 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')]) + ])); + } + } +} diff --git a/angel_orm_generator/lib/src/builder/postgres/build_context.dart b/angel_orm_generator/lib/src/builder/postgres/build_context.dart deleted file mode 100644 index c29d3cd7..00000000 --- a/angel_orm_generator/lib/src/builder/postgres/build_context.dart +++ /dev/null @@ -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 fieldNames = []; - List fields = []; - - for (var field in raw.fields) { - fieldNames.add(field.name); - // Check for relationship. If so, skip. - var relationshipAnnotation = relations.firstAnnotationOf(field); - /* findAnnotation(field, HasOne) ?? - findAnnotation(field, HasMany) ?? - findAnnotation(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(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); -} diff --git a/angel_orm_generator/lib/src/builder/postgres/migration.dart b/angel_orm_generator/lib/src/builder/postgres/migration.dart deleted file mode 100644 index ff6f1120..00000000 --- a/angel_orm_generator/lib/src/builder/postgres/migration.dart +++ /dev/null @@ -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> 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 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}";'); - } -} diff --git a/angel_orm_generator/lib/src/builder/postgres/postgres.dart b/angel_orm_generator/lib/src/builder/postgres/postgres.dart deleted file mode 100644 index 7ed73a7e..00000000 --- a/angel_orm_generator/lib/src/builder/postgres/postgres.dart +++ /dev/null @@ -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 RELATIONS = const ['or']; -const List RESTRICTORS = const ['limit', 'offset']; -const Map SORT_MODES = const { - 'Descending': 'DESC', - 'Ascending': 'ASC' -}; - -// TODO: HasOne, HasMany -class PostgresORMGenerator extends GeneratorForAnnotation { - /// 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 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 contexts = {}; - List done = []; - List 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 = {'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 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 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 _buildSubstitutionValues( - PostgresBuildContext ctx) { - Map 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 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 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 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 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 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; - } -} 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 deleted file mode 100644 index 4ef240aa..00000000 --- a/angel_orm_generator/lib/src/builder/postgres/postgres_build_context.dart +++ /dev/null @@ -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 _populatedRelationships = {}; - final Map columnInfo = {}; - final Map indices = {}; - final Map 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 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 get aliases => raw.aliases; - - Map 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`, 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); - } -} diff --git a/angel_orm_generator/tool/phases.dart b/angel_orm_generator/tool/phases.dart index 212bbb8d..f0fd1873 100644 --- a/angel_orm_generator/tool/phases.dart +++ b/angel_orm_generator/tool/phases.dart @@ -33,5 +33,10 @@ final PhaseGroup PHASES = new PhaseGroup() new GeneratorBuilder([new PostgresORMGenerator()], isStandalone: true, generatedExtension: '.orm.g.dart'), DEPENDENT_MODELS)) + ..addPhase(new Phase() + ..addAction( + new GeneratorBuilder([new PostgresServiceGenerator()], + isStandalone: true, generatedExtension: '.service.g.dart'), + ALL_MODELS)) ..addPhase(new Phase() ..addAction(new SQLMigrationGenerator(temporary: true), ALL_MODELS));