diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 00000000..80e9d968 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +orm \ No newline at end of file diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml new file mode 100644 index 00000000..11ef66c4 --- /dev/null +++ b/.idea/dbnavigator.xml @@ -0,0 +1,451 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/orm.iml b/.idea/orm.iml index ab4131d2..d4c62765 100644 --- a/.idea/orm.iml +++ b/.idea/orm.iml @@ -1,24 +1,14 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + diff --git a/.idea/runConfigurations/postgres__build_dart.xml b/.idea/runConfigurations/postgres__build_dart.xml index 8fec7c79..406d1d50 100644 --- a/.idea/runConfigurations/postgres__build_dart.xml +++ b/.idea/runConfigurations/postgres__build_dart.xml @@ -1,7 +1,7 @@ - \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index de2210c9..cf34b7d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,7 @@ -language: dart \ No newline at end of file +language: dart +script: bash tool/.travis.sh +before_script: + - psql -c 'create database angel_orm_test;' -U postgres + - psql -c "CREATE USER angel_orm WITH PASSWORD 'angel_orm';" -U postgres +services: + - postgresql \ No newline at end of file diff --git a/README.md b/README.md index 8f176569..197966f9 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,31 @@ [![Pub](https://img.shields.io/pub/v/angel_orm.svg)](https://pub.dartlang.org/packages/angel_orm) [![build status](https://travis-ci.org/angel-dart/orm.svg)](https://travis-ci.org/angel-dart/orm) -**This project is currently in the early stages, and may change at any given -time without warning.** - Source-generated PostgreSQL ORM for use with the [Angel framework](https://angel-dart.github.io). Now you can combine the power and flexibility of Angel with a strongly-typed ORM. -Currently supported: -* PostgreSQL +* [Usage](#usage) +* [Model Definitions](#models) +* [MVC Example](#example) +* [Relationships](#relations) + +# Usage +You'll need these dependencies in your `pubspec.yaml`: +```yaml +dependencies: + angel_orm: ^1.0.0-alpha +dev_dependencies: + angel_orm_generator: ^1.0.0-alpha + build_runner: ^0.3.0 +``` + +`package:angel_orm_generator` exports two classes that you can include +in a `package:build` flow: +* `PostgreORMGenerator` - Fueled by `package:source_gen`; include this within a `GeneratorBuilder`. +* `SQLMigrationGenerator` - This is its own `Builder`; it generates a SQL schema, as well as a SQL script to drop a generated table. + +You should pass an `InputSet` containing your project's models. # Models Your model, courtesy of `package:angel_serialize`: @@ -38,6 +54,13 @@ Models can use the `@Alias()` annotation; `package:angel_orm` obeys it. After building, you'll have access to a `Query` class with strongly-typed methods that allow to run asynchronous queries without a headache. +**IMPORTANT:** The ORM *assumes* that you are using `package:angel_serialize`, and will only generate code +designed for such a workflow. Save yourself a headache and build models with `angel_serialize`: + +https://github.com/angel-dart/serialize + +# Example + MVC just got a whole lot easier: ```dart @@ -91,7 +114,7 @@ class CarService extends Controller { ``` # Relations -**NOTE**: This is not yet implemented. +**NOTE**: This is not yet implemented. Expect to see more documentation about this soon. * `@HasOne()` * `@HasMany()` diff --git a/.analysis-options b/angel_orm/.analysis-options similarity index 100% rename from .analysis-options rename to angel_orm/.analysis-options diff --git a/angel_orm/.gitignore b/angel_orm/.gitignore new file mode 100644 index 00000000..99e7978e --- /dev/null +++ b/angel_orm/.gitignore @@ -0,0 +1,56 @@ +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.packages +.pub/ +build/ +# If you're building an application, you may want to check-in your pubspec.lock +pubspec.lock + +# Directory created by dartdoc +# If you don't generate documentation locally you can remove this line. +doc/api/ +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties diff --git a/LICENSE b/angel_orm/LICENSE similarity index 100% rename from LICENSE rename to angel_orm/LICENSE diff --git a/angel_orm/README.md b/angel_orm/README.md new file mode 100644 index 00000000..5546e559 --- /dev/null +++ b/angel_orm/README.md @@ -0,0 +1,7 @@ +# angel_orm +Runtime support for Angel's ORM. Includes SQL expression generators, as well +as a friendly `PostgreSQLConnectionPool` class that you can use to pool connections +to a PostgreSQL database. + +For documentation about the ORM, head to the main project repo: +https://github.com/angel-dart/orm \ No newline at end of file diff --git a/lib/angel_orm.dart b/angel_orm/lib/angel_orm.dart similarity index 100% rename from lib/angel_orm.dart rename to angel_orm/lib/angel_orm.dart diff --git a/lib/src/annotations.dart b/angel_orm/lib/src/annotations.dart similarity index 100% rename from lib/src/annotations.dart rename to angel_orm/lib/src/annotations.dart diff --git a/lib/src/migration.dart b/angel_orm/lib/src/migration.dart similarity index 100% rename from lib/src/migration.dart rename to angel_orm/lib/src/migration.dart diff --git a/lib/src/pool.dart b/angel_orm/lib/src/pool.dart similarity index 100% rename from lib/src/pool.dart rename to angel_orm/lib/src/pool.dart diff --git a/lib/src/query.dart b/angel_orm/lib/src/query.dart similarity index 100% rename from lib/src/query.dart rename to angel_orm/lib/src/query.dart diff --git a/lib/src/relations.dart b/angel_orm/lib/src/relations.dart similarity index 100% rename from lib/src/relations.dart rename to angel_orm/lib/src/relations.dart diff --git a/angel_orm/pubspec.yaml b/angel_orm/pubspec.yaml new file mode 100644 index 00000000..f1453319 --- /dev/null +++ b/angel_orm/pubspec.yaml @@ -0,0 +1,11 @@ +name: angel_orm +version: 1.0.0-alpha +description: Runtime support for Angel's ORM. +author: Tobe O +homepage: https://github.com/angel-dart/orm +environment: + sdk: ">=1.19.0" +dependencies: + intl: ^0.15.1 + pool: ^1.0.0 + postgres: ^0.9.5 \ No newline at end of file diff --git a/angel_orm_generator/.analysis-options b/angel_orm_generator/.analysis-options new file mode 100644 index 00000000..518eb901 --- /dev/null +++ b/angel_orm_generator/.analysis-options @@ -0,0 +1,2 @@ +analyzer: + strong-mode: true \ No newline at end of file diff --git a/angel_orm_generator/.gitignore b/angel_orm_generator/.gitignore new file mode 100644 index 00000000..99e7978e --- /dev/null +++ b/angel_orm_generator/.gitignore @@ -0,0 +1,56 @@ +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.packages +.pub/ +build/ +# If you're building an application, you may want to check-in your pubspec.lock +pubspec.lock + +# Directory created by dartdoc +# If you don't generate documentation locally you can remove this line. +doc/api/ +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties diff --git a/angel_orm_generator/LICENSE b/angel_orm_generator/LICENSE new file mode 100644 index 00000000..89074fd3 --- /dev/null +++ b/angel_orm_generator/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 The Angel Framework + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/angel_orm_generator/README.md b/angel_orm_generator/README.md new file mode 100644 index 00000000..9ba833f7 --- /dev/null +++ b/angel_orm_generator/README.md @@ -0,0 +1,8 @@ +# angel_orm_generator +Source code generators for Angel's ORM. +This package can generate: +* A strongly-typed ORM +* SQL migration scripts + +For documentation about the ORM, head to the main project repo: +https://github.com/angel-dart/orm \ No newline at end of file diff --git a/lib/builder.dart b/angel_orm_generator/lib/angel_orm_generator.dart similarity index 100% rename from lib/builder.dart rename to angel_orm_generator/lib/angel_orm_generator.dart diff --git a/lib/src/builder/postgres/build_context.dart b/angel_orm_generator/lib/src/builder/postgres/build_context.dart similarity index 90% rename from lib/src/builder/postgres/build_context.dart rename to angel_orm_generator/lib/src/builder/postgres/build_context.dart index c67377a1..9efe8a12 100644 --- a/lib/src/builder/postgres/build_context.dart +++ b/angel_orm_generator/lib/src/builder/postgres/build_context.dart @@ -1,18 +1,14 @@ import 'package:analyzer/dart/element/element.dart'; -import 'package:angel_serialize/build_context.dart' as serialize; -import 'package:angel_serialize/context.dart' as serialize; +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 '../../annotations.dart'; -import '../../migration.dart'; -import '../../relations.dart'; -import 'package:angel_serialize/src/find_annotation.dart'; -import 'package:source_gen/src/annotation.dart'; import 'package:source_gen/source_gen.dart'; import 'postgres_build_context.dart'; -// TODO: Should add id, createdAt, updatedAt... PostgresBuildContext buildContext( ClassElement clazz, ORM annotation, diff --git a/lib/src/builder/postgres/migration.dart b/angel_orm_generator/lib/src/builder/postgres/migration.dart similarity index 87% rename from lib/src/builder/postgres/migration.dart rename to angel_orm_generator/lib/src/builder/postgres/migration.dart index 6f3d9644..b6461361 100644 --- a/lib/src/builder/postgres/migration.dart +++ b/angel_orm_generator/lib/src/builder/postgres/migration.dart @@ -1,19 +1,9 @@ import 'dart:async'; import 'package:analyzer/dart/element/element.dart'; -import 'package:angel_serialize/angel_serialize.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 '../../annotations.dart'; -import '../../migration.dart'; -import 'package:angel_serialize/src/find_annotation.dart'; import 'build_context.dart'; import 'postgres_build_context.dart'; diff --git a/lib/src/builder/postgres/postgres.dart b/angel_orm_generator/lib/src/builder/postgres/postgres.dart similarity index 82% rename from lib/src/builder/postgres/postgres.dart rename to angel_orm_generator/lib/src/builder/postgres/postgres.dart index da0202ba..56b035a2 100644 --- a/lib/src/builder/postgres/postgres.dart +++ b/angel_orm_generator/lib/src/builder/postgres/postgres.dart @@ -1,19 +1,14 @@ import 'dart:async'; import 'package:analyzer/dart/element/element.dart'; -import 'package:angel_serialize/angel_serialize.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 '../../annotations.dart'; -import '../../migration.dart'; -import 'package:angel_serialize/src/find_annotation.dart'; import 'build_context.dart'; import 'postgres_build_context.dart'; @@ -154,6 +149,12 @@ class PostgresORMGenerator extends GeneratorForAnnotation { // 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', @@ -315,7 +316,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation { } void _addReturning(StringBuffer buf, PostgresBuildContext ctx) { - buf.write(' RETURNING ('); + buf.write(' RETURNING '); int i = 0; ctx.fields.forEach((field) { if (i++ > 0) buf.write(', '); @@ -323,7 +324,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation { buf.write('"$name"'); }); - buf.write(');'); + buf.write(';'); } void _ensureDates(MethodBuilder meth, PostgresBuildContext ctx) { @@ -424,6 +425,44 @@ class PostgresORMGenerator extends GeneratorForAnnotation { genericTypes: [new TypeBuilder(ctx.modelClassName)])); meth.addPositional( parameter('connection', [new TypeBuilder('PostgreSQLConnection')])); + var buf = reference('buf'), whereClause = reference('whereClause'); + + meth.addStatement(varField('buf', + value: lib$core.StringBuffer + .newInstance([literal('DELETE FROM "${ctx.tableName}"')]))); + meth.addStatement(varField('whereClause', + value: reference('where').invoke('toWhereClause', []))); + + var ifStmt = ifThen(whereClause.notEquals(literal(null)), [ + buf.invoke('write', [literal(' ') + whereClause]) + ]); + meth.addStatement(ifStmt); + + for (var relation in RELATIONS) { + var ref = reference('_$relation'); + var upper = relation.toUpperCase(); + ifStmt.addStatement(ifThen(ref.property('isNotEmpty'), [ + buf.invoke('write', [ + literal(' $upper (') + + ref.invoke('join', [literal(', ')]) + + literal(')') + ]) + ])); + } + + var litBuf = new StringBuffer(); + _addReturning(litBuf, ctx); + meth.addStatement(buf.invoke('write', [literal(litBuf.toString())])); + + var streamController = new TypeBuilder('StreamController', + genericTypes: [new TypeBuilder(ctx.modelClassName)]); + meth.addStatement(varField('ctrl', + type: streamController, value: streamController.newInstance([]))); + + var future = + reference('connection').invoke('query', [buf.invoke('toString', [])]); + _invokeStreamClosure(future, meth); + return meth; } @@ -438,32 +477,21 @@ class PostgresORMGenerator extends GeneratorForAnnotation { var id = reference('id'); var connection = reference('connection'); - var beforeDelete = reference('__ormBeforeDelete__'); var result = reference('result'); - // var __ormBeforeDelete__ = await XQuery.getOne(id, connection); - meth.addStatement(varField('__ormBeforeDelete__', - value: new TypeBuilder(ctx.queryClassName) - .invoke('getOne', [id, connection]).asAwait())); + var buf = new StringBuffer('DELETE FROM "${ctx.tableName}" WHERE id = @id'); + _addReturning(buf, ctx); // await connection.execute('...'); meth.addStatement(varField('result', - value: connection.invoke('execute', [ - literal('DELETE FROM "${ctx.tableName}" WHERE id = @id;') + value: connection.invoke('query', [ + literal(buf.toString()) ], namedArguments: { 'substitutionValues': map({'id': id}) }).asAwait())); - meth.addStatement(ifThen(result.notEquals(literal(1)), [ - lib$core.StateError.newInstance([ - literal('DELETE query deleted ') + - result + - literal(' row(s), instead of exactly 1 row.') - ]) - ])); - - // return __ormBeforeDelete__ - meth.addStatement(beforeDelete.asReturn()); + meth.addStatement( + reference('parseRow').call([result[literal(0)]]).asReturn()); return meth; } @@ -501,43 +529,85 @@ class PostgresORMGenerator extends GeneratorForAnnotation { } }); - buf.write(');'); + buf.write(')'); // meth.addStatement(lib$core.print.call([literal(buf.toString())])); + _addReturning(buf, ctx); _ensureDates(meth, ctx); var substitutionValues = _buildSubstitutionValues(ctx); - // connection.execute... - var connection = reference('connection'), nRows = reference('nRows'); - meth.addStatement(varField('nRows', - value: connection.invoke('execute', [ - literal(buf.toString()) + var connection = reference('connection'); + var query = literal(buf.toString()); + var result = reference('result'); + meth.addStatement(varField('result', + value: connection.invoke('query', [ + query ], namedArguments: { 'substitutionValues': map(substitutionValues) }).asAwait())); + meth.addStatement( + reference('parseRow').call([result[literal(0)]]).asReturn()); + return meth; + } - meth.addStatement(ifThen(nRows < literal(1), [ - lib$core.StateError.newInstance([ - literal('Insertion into "${ctx.tableName}" table failed.') - ]).asThrow() + MethodBuilder buildInsertModelMethod(PostgresBuildContext ctx) { + var rc = new ReCase(ctx.modelClassName); + var meth = new MethodBuilder('insert${rc.pascalCase}', + returnType: new TypeBuilder('Future', + genericTypes: [new TypeBuilder(ctx.modelClassName)])); + + meth.addPositional( + parameter('connection', [new TypeBuilder('PostgreSQLConnection')])); + meth.addPositional( + parameter(rc.snakeCase, [new TypeBuilder(ctx.modelClassName)])); + + Map args = {}; + var ref = reference(rc.snakeCase); + + ctx.fields.forEach((f) { + if (f.name != 'id') args[f.name] = ref.property(f.name); + }); + + meth.addStatement(new TypeBuilder(ctx.queryClassName) + .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: [new TypeBuilder(ctx.modelClassName)])); + + meth.addPositional( + parameter('connection', [new TypeBuilder('PostgreSQLConnection')])); + meth.addPositional( + parameter(rc.snakeCase, [new TypeBuilder(ctx.modelClassName)])); + + // var query = new XQuery(); + var ref = reference(rc.snakeCase); + var query = reference('query'); + meth.addStatement(varField('query', + value: new TypeBuilder(ctx.queryClassName).newInstance([]))); + + // query.where.id.equals(x.id); + meth.addStatement(query.property('where').property('id').invoke('equals', [ + lib$core.int.invoke('parse', [ref.property('id')]) ])); - // Query the last value... - /* - var currval = await connection.query("SELECT * FROM cars WHERE id = currval(pg_get_serial_sequence('cars', 'id'));"); - print(currval); - return parseRow(currval[0]); - */ + // return query.update(connection, ...).first; + Map args = {}; + ctx.fields.forEach((f) { + if (f.name != 'id') args[f.name] = ref.property(f.name); + }); + + var update = + query.invoke('update', [reference('connection')], namedArguments: args); + meth.addStatement(update.property('first').asReturn()); - var currVal = reference('currVal'); - meth.addStatement(varField('currVal', - value: connection.invoke('query', [ - literal( - 'SELECT * FROM "${ctx.tableName}" WHERE id = currval(pg_get_serial_sequence(\'${ctx.tableName}\', \'id\'));') - ]).asAwait())); - meth.addStatement( - reference('parseRow').call([currVal[literal(0)]]).asReturn()); return meth; } diff --git a/lib/src/builder/postgres/postgres_build_context.dart b/angel_orm_generator/lib/src/builder/postgres/postgres_build_context.dart similarity index 92% rename from lib/src/builder/postgres/postgres_build_context.dart rename to angel_orm_generator/lib/src/builder/postgres/postgres_build_context.dart index 3b6da148..9a52805a 100644 --- a/lib/src/builder/postgres/postgres_build_context.dart +++ b/angel_orm_generator/lib/src/builder/postgres/postgres_build_context.dart @@ -1,11 +1,9 @@ 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:angel_serialize/context.dart'; -import '../../annotations.dart'; -import '../../migration.dart'; -import '../../relations.dart'; class PostgresBuildContext extends BuildContext { DartType _dateTimeTypeCache; diff --git a/angel_orm_generator/pubspec.yaml b/angel_orm_generator/pubspec.yaml new file mode 100644 index 00000000..eb3b7f5e --- /dev/null +++ b/angel_orm_generator/pubspec.yaml @@ -0,0 +1,20 @@ +name: angel_orm_generator +version: 1.0.0-alpha +description: Code generators for Angel's ORM. +author: Tobe O +homepage: https://github.com/angel-dart/orm +environment: + sdk: ">=1.19.0" +dependencies: + angel_orm: ^1.0.0-alpha + angel_serialize_generator: ^1.0.0-alpha + code_builder: ^1.0.0 + inflection: ^0.4.1 + recase: ^1.0.0 + source_gen: ^0.6.0 +dev_dependencies: + angel_diagnostics: ^1.0.0 + angel_framework: ^1.0.0 + angel_test: ^1.0.0 + build_runner: ^0.3.0 + test: ^0.12.0 \ No newline at end of file diff --git a/test/car_test.dart b/angel_orm_generator/test/car_test.dart similarity index 75% rename from test/car_test.dart rename to angel_orm_generator/test/car_test.dart index 131f4e66..626202d4 100644 --- a/test/car_test.dart +++ b/angel_orm_generator/test/car_test.dart @@ -106,12 +106,26 @@ main() { expect(cars, isEmpty); }); - test('delete', () async { - var query = new CarQuery(); + test('delete stream', () async { + var query = new CarQuery()..where.make.equals('Ferrari'); var cars = await query.delete(connection).toList(); expect(cars, hasLength(1)); expect(cars.first.toJson(), ferrari.toJson()); }); + + test('update', () async { + var query = new CarQuery()..where.id.equals(int.parse(ferrari.id)); + var cars = await query.update(connection, make: 'Hyundai').toList(); + expect(cars, hasLength(1)); + expect(cars.first.make, 'Hyundai'); + }); + + test('update car', () async { + var cloned = ferrari.clone()..make = 'Angel'; + var car = await CarQuery.updateCar(connection, cloned); + print(car.toJson()); + expect(car.toJson(), cloned.toJson()); + }); }); }); @@ -126,8 +140,25 @@ main() { expect(car.make, 'Honda'); expect(car.description, 'Hello'); expect(car.familyFriendly, isTrue); - expect(DATE_YMD_HMS.format(car.recalledAt), DATE_YMD_HMS.format(recalledAt)); + expect( + DATE_YMD_HMS.format(car.recalledAt), DATE_YMD_HMS.format(recalledAt)); expect(car.createdAt, allOf(isNotNull, equals(car.updatedAt))); }); + + test('insert car', () async { + var recalledAt = new DateTime.now(); + var beetle = new Car( + make: 'Beetle', + description: 'Herbie', + familyFriendly: true, + recalledAt: recalledAt); + var car = await CarQuery.insertCar(connection, beetle); + print(car.toJson()); + expect(car.make, beetle.make); + expect(car.description, beetle.description); + expect(car.familyFriendly, beetle.familyFriendly); + expect(DATE_YMD_HMS.format(car.recalledAt), + DATE_YMD_HMS.format(beetle.recalledAt)); + }); }); } diff --git a/test/common.dart b/angel_orm_generator/test/common.dart similarity index 100% rename from test/common.dart rename to angel_orm_generator/test/common.dart diff --git a/test/models/author.dart b/angel_orm_generator/test/models/author.dart similarity index 100% rename from test/models/author.dart rename to angel_orm_generator/test/models/author.dart diff --git a/test/models/author.down.g.sql b/angel_orm_generator/test/models/author.down.g.sql similarity index 100% rename from test/models/author.down.g.sql rename to angel_orm_generator/test/models/author.down.g.sql diff --git a/test/models/author.g.dart b/angel_orm_generator/test/models/author.g.dart similarity index 95% rename from test/models/author.g.dart rename to angel_orm_generator/test/models/author.g.dart index bce01bc1..3c2642ae 100644 --- a/test/models/author.g.dart +++ b/angel_orm_generator/test/models/author.g.dart @@ -46,4 +46,8 @@ class Author extends _Author { }; static Author parse(Map map) => new Author.fromJson(map); + + Author clone() { + return new Author.fromJson(toJson()); + } } diff --git a/test/models/author.orm.g.dart b/angel_orm_generator/test/models/author.orm.g.dart similarity index 72% rename from test/models/author.orm.g.dart rename to angel_orm_generator/test/models/author.orm.g.dart index a313c229..a690bdbd 100644 --- a/test/models/author.orm.g.dart +++ b/angel_orm_generator/test/models/author.orm.g.dart @@ -95,8 +95,7 @@ class AuthorQuery { var __ormNow__ = new DateTime.now(); var ctrl = new StreamController(); connection.query( - buf.toString() + - ' RETURNING ("id", "name", "created_at", "updated_at");', + buf.toString() + ' RETURNING "id", "name", "created_at", "updated_at";', substitutionValues: { 'name': name, 'createdAt': createdAt != null ? createdAt : __ormNow__, @@ -108,38 +107,69 @@ class AuthorQuery { return ctrl.stream; } - Stream delete(PostgreSQLConnection connection) async {} + Stream delete(PostgreSQLConnection connection) { + var buf = new StringBuffer('DELETE FROM "authors"'); + var whereClause = where.toWhereClause(); + if (whereClause != null) { + buf.write(' ' + whereClause); + if (_and.isNotEmpty) { + buf.write(' AND (' + _and.join(', ') + ')'); + } + if (_or.isNotEmpty) { + buf.write(' OR (' + _or.join(', ') + ')'); + } + if (_not.isNotEmpty) { + buf.write(' NOT (' + _not.join(', ') + ')'); + } + } + buf.write(' RETURNING "id", "name", "created_at", "updated_at";'); + StreamController ctrl = new StreamController(); + connection.query(buf.toString()).then((rows) { + rows.map(parseRow).forEach(ctrl.add); + ctrl.close(); + }).catchError(ctrl.addError); + return ctrl.stream; + } static Future deleteOne( int id, PostgreSQLConnection connection) async { - var __ormBeforeDelete__ = await AuthorQuery.getOne(id, connection); - var result = await connection.execute( - 'DELETE FROM "authors" WHERE id = @id;', + var result = await connection.query( + 'DELETE FROM "authors" WHERE id = @id RETURNING "id", "name", "created_at", "updated_at";', substitutionValues: {'id': id}); - if (result != 1) { - new StateError('DELETE query deleted ' + - result + - ' row(s), instead of exactly 1 row.'); - } - return __ormBeforeDelete__; + return parseRow(result[0]); } static Future insert(PostgreSQLConnection connection, {String name, DateTime createdAt, DateTime updatedAt}) async { var __ormNow__ = new DateTime.now(); - var nRows = await connection.execute( - 'INSERT INTO "authors" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt);', + var result = await connection.query( + 'INSERT INTO "authors" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt) RETURNING "id", "name", "created_at", "updated_at";', substitutionValues: { 'name': name, 'createdAt': createdAt != null ? createdAt : __ormNow__, 'updatedAt': updatedAt != null ? updatedAt : __ormNow__ }); - if (nRows < 1) { - throw new StateError('Insertion into "authors" table failed.'); - } - var currVal = await connection.query( - 'SELECT * FROM "authors" WHERE id = currval(pg_get_serial_sequence(\'authors\', \'id\'));'); - return parseRow(currVal[0]); + return parseRow(result[0]); + } + + static Future insertAuthor( + PostgreSQLConnection connection, Author author) { + return AuthorQuery.insert(connection, + name: author.name, + createdAt: author.createdAt, + updatedAt: author.updatedAt); + } + + static Future updateAuthor( + PostgreSQLConnection connection, Author author) { + var query = new AuthorQuery(); + query.where.id.equals(int.parse(author.id)); + return query + .update(connection, + name: author.name, + createdAt: author.createdAt, + updatedAt: author.updatedAt) + .first; } static Stream getAll(PostgreSQLConnection connection) => diff --git a/test/models/author.up.g.sql b/angel_orm_generator/test/models/author.up.g.sql similarity index 100% rename from test/models/author.up.g.sql rename to angel_orm_generator/test/models/author.up.g.sql diff --git a/test/models/book.dart b/angel_orm_generator/test/models/book.dart similarity index 100% rename from test/models/book.dart rename to angel_orm_generator/test/models/book.dart diff --git a/test/models/book.down.g.sql b/angel_orm_generator/test/models/book.down.g.sql similarity index 100% rename from test/models/book.down.g.sql rename to angel_orm_generator/test/models/book.down.g.sql diff --git a/test/models/book.g.dart b/angel_orm_generator/test/models/book.g.dart similarity index 95% rename from test/models/book.g.dart rename to angel_orm_generator/test/models/book.g.dart index 0df44c1c..7f2c01df 100644 --- a/test/models/book.g.dart +++ b/angel_orm_generator/test/models/book.g.dart @@ -51,4 +51,8 @@ class Book extends _Book { }; static Book parse(Map map) => new Book.fromJson(map); + + Book clone() { + return new Book.fromJson(toJson()); + } } diff --git a/test/models/book.orm.g.dart b/angel_orm_generator/test/models/book.orm.g.dart similarity index 73% rename from test/models/book.orm.g.dart rename to angel_orm_generator/test/models/book.orm.g.dart index c460ea48..20e2e17b 100644 --- a/test/models/book.orm.g.dart +++ b/angel_orm_generator/test/models/book.orm.g.dart @@ -97,8 +97,7 @@ class BookQuery { var __ormNow__ = new DateTime.now(); var ctrl = new StreamController(); connection.query( - buf.toString() + - ' RETURNING ("id", "name", "created_at", "updated_at");', + buf.toString() + ' RETURNING "id", "name", "created_at", "updated_at";', substitutionValues: { 'name': name, 'createdAt': createdAt != null ? createdAt : __ormNow__, @@ -110,36 +109,64 @@ class BookQuery { return ctrl.stream; } - Stream delete(PostgreSQLConnection connection) async {} + Stream delete(PostgreSQLConnection connection) { + var buf = new StringBuffer('DELETE FROM "books"'); + var whereClause = where.toWhereClause(); + if (whereClause != null) { + buf.write(' ' + whereClause); + if (_and.isNotEmpty) { + buf.write(' AND (' + _and.join(', ') + ')'); + } + if (_or.isNotEmpty) { + buf.write(' OR (' + _or.join(', ') + ')'); + } + if (_not.isNotEmpty) { + buf.write(' NOT (' + _not.join(', ') + ')'); + } + } + buf.write(' RETURNING "id", "name", "created_at", "updated_at";'); + StreamController ctrl = new StreamController(); + connection.query(buf.toString()).then((rows) { + rows.map(parseRow).forEach(ctrl.add); + ctrl.close(); + }).catchError(ctrl.addError); + return ctrl.stream; + } static Future deleteOne(int id, PostgreSQLConnection connection) async { - var __ormBeforeDelete__ = await BookQuery.getOne(id, connection); - var result = await connection.execute('DELETE FROM "books" WHERE id = @id;', + var result = await connection.query( + 'DELETE FROM "books" WHERE id = @id RETURNING "id", "name", "created_at", "updated_at";', substitutionValues: {'id': id}); - if (result != 1) { - new StateError('DELETE query deleted ' + - result + - ' row(s), instead of exactly 1 row.'); - } - return __ormBeforeDelete__; + return parseRow(result[0]); } static Future insert(PostgreSQLConnection connection, {String name, DateTime createdAt, DateTime updatedAt}) async { var __ormNow__ = new DateTime.now(); - var nRows = await connection.execute( - 'INSERT INTO "books" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt);', + var result = await connection.query( + 'INSERT INTO "books" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt) RETURNING "id", "name", "created_at", "updated_at";', substitutionValues: { 'name': name, 'createdAt': createdAt != null ? createdAt : __ormNow__, 'updatedAt': updatedAt != null ? updatedAt : __ormNow__ }); - if (nRows < 1) { - throw new StateError('Insertion into "books" table failed.'); - } - var currVal = await connection.query( - 'SELECT * FROM "books" WHERE id = currval(pg_get_serial_sequence(\'books\', \'id\'));'); - return parseRow(currVal[0]); + return parseRow(result[0]); + } + + static Future insertBook(PostgreSQLConnection connection, Book book) { + return BookQuery.insert(connection, + name: book.name, createdAt: book.createdAt, updatedAt: book.updatedAt); + } + + static Future updateBook(PostgreSQLConnection connection, Book book) { + var query = new BookQuery(); + query.where.id.equals(int.parse(book.id)); + return query + .update(connection, + name: book.name, + createdAt: book.createdAt, + updatedAt: book.updatedAt) + .first; } static Stream getAll(PostgreSQLConnection connection) => diff --git a/test/models/book.up.g.sql b/angel_orm_generator/test/models/book.up.g.sql similarity index 100% rename from test/models/book.up.g.sql rename to angel_orm_generator/test/models/book.up.g.sql diff --git a/test/models/car.dart b/angel_orm_generator/test/models/car.dart similarity index 100% rename from test/models/car.dart rename to angel_orm_generator/test/models/car.dart diff --git a/test/models/car.down.g.sql b/angel_orm_generator/test/models/car.down.g.sql similarity index 100% rename from test/models/car.down.g.sql rename to angel_orm_generator/test/models/car.down.g.sql diff --git a/test/models/car.g.dart b/angel_orm_generator/test/models/car.g.dart similarity index 97% rename from test/models/car.g.dart rename to angel_orm_generator/test/models/car.g.dart index 2425d12e..fd3f30f5 100644 --- a/test/models/car.g.dart +++ b/angel_orm_generator/test/models/car.g.dart @@ -72,4 +72,8 @@ class Car extends _Car { }; static Car parse(Map map) => new Car.fromJson(map); + + Car clone() { + return new Car.fromJson(toJson()); + } } diff --git a/test/models/car.orm.g.dart b/angel_orm_generator/test/models/car.orm.g.dart similarity index 76% rename from test/models/car.orm.g.dart rename to angel_orm_generator/test/models/car.orm.g.dart index 827edc82..a4783e56 100644 --- a/test/models/car.orm.g.dart +++ b/angel_orm_generator/test/models/car.orm.g.dart @@ -104,7 +104,7 @@ class CarQuery { var ctrl = new StreamController(); connection.query( buf.toString() + - ' RETURNING ("id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at");', + ' RETURNING "id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at";', substitutionValues: { 'make': make, 'description': description, @@ -120,10 +120,24 @@ class CarQuery { } Stream delete(PostgreSQLConnection connection) { - var query = 'DELETE FROM "cars" RETURNING ("id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at");'; + var buf = new StringBuffer('DELETE FROM "cars"'); + var whereClause = where.toWhereClause(); + if (whereClause != null) { + buf.write(' ' + whereClause); + if (_and.isNotEmpty) { + buf.write(' AND (' + _and.join(', ') + ')'); + } + if (_or.isNotEmpty) { + buf.write(' OR (' + _or.join(', ') + ')'); + } + if (_not.isNotEmpty) { + buf.write(' NOT (' + _not.join(', ') + ')'); + } + } + buf.write( + ' RETURNING "id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at";'); StreamController ctrl = new StreamController(); - connection.execute(query).then((rows) { - print('Rows: $rows'); + connection.query(buf.toString()).then((rows) { rows.map(parseRow).forEach(ctrl.add); ctrl.close(); }).catchError(ctrl.addError); @@ -131,15 +145,10 @@ class CarQuery { } static Future deleteOne(int id, PostgreSQLConnection connection) async { - var __ormBeforeDelete__ = await CarQuery.getOne(id, connection); - var result = await connection.execute('DELETE FROM "cars" WHERE id = @id;', + var result = await connection.query( + 'DELETE FROM "cars" WHERE id = @id RETURNING "id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at";', substitutionValues: {'id': id}); - if (result != 1) { - new StateError('DELETE query deleted ' + - result + - ' row(s), instead of exactly 1 row.'); - } - return __ormBeforeDelete__; + return parseRow(result[0]); } static Future insert(PostgreSQLConnection connection, @@ -150,8 +159,8 @@ class CarQuery { DateTime createdAt, DateTime updatedAt}) async { var __ormNow__ = new DateTime.now(); - var nRows = await connection.execute( - 'INSERT INTO "cars" ("make", "description", "family_friendly", "recalled_at", "created_at", "updated_at") VALUES (@make, @description, @familyFriendly, @recalledAt, @createdAt, @updatedAt);', + var result = await connection.query( + 'INSERT INTO "cars" ("make", "description", "family_friendly", "recalled_at", "created_at", "updated_at") VALUES (@make, @description, @familyFriendly, @recalledAt, @createdAt, @updatedAt) RETURNING "id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at";', substitutionValues: { 'make': make, 'description': description, @@ -160,12 +169,31 @@ class CarQuery { 'createdAt': createdAt != null ? createdAt : __ormNow__, 'updatedAt': updatedAt != null ? updatedAt : __ormNow__ }); - if (nRows < 1) { - throw new StateError('Insertion into "cars" table failed.'); - } - var currVal = await connection.query( - 'SELECT * FROM "cars" WHERE id = currval(pg_get_serial_sequence(\'cars\', \'id\'));'); - return parseRow(currVal[0]); + return parseRow(result[0]); + } + + static Future insertCar(PostgreSQLConnection connection, Car car) { + return CarQuery.insert(connection, + make: car.make, + description: car.description, + familyFriendly: car.familyFriendly, + recalledAt: car.recalledAt, + createdAt: car.createdAt, + updatedAt: car.updatedAt); + } + + static Future updateCar(PostgreSQLConnection connection, Car car) { + var query = new CarQuery(); + query.where.id.equals(int.parse(car.id)); + return query + .update(connection, + make: car.make, + description: car.description, + familyFriendly: car.familyFriendly, + recalledAt: car.recalledAt, + createdAt: car.createdAt, + updatedAt: car.updatedAt) + .first; } static Stream getAll(PostgreSQLConnection connection) => diff --git a/test/models/car.up.g.sql b/angel_orm_generator/test/models/car.up.g.sql similarity index 100% rename from test/models/car.up.g.sql rename to angel_orm_generator/test/models/car.up.g.sql diff --git a/tool/build.dart b/angel_orm_generator/tool/build.dart similarity index 100% rename from tool/build.dart rename to angel_orm_generator/tool/build.dart diff --git a/tool/phases.dart b/angel_orm_generator/tool/phases.dart similarity index 69% rename from tool/phases.dart rename to angel_orm_generator/tool/phases.dart index e1300396..78c2dd59 100644 --- a/tool/phases.dart +++ b/angel_orm_generator/tool/phases.dart @@ -1,9 +1,10 @@ import 'package:build_runner/build_runner.dart'; import 'package:source_gen/source_gen.dart'; -import 'package:angel_orm/builder.dart'; -import 'package:angel_serialize/builder.dart'; +import 'package:angel_orm_generator/angel_orm_generator.dart'; +import 'package:angel_serialize_generator/angel_serialize_generator.dart'; -final InputSet MODELS = new InputSet('angel_orm', const ['test/models/*.dart']); +final InputSet MODELS = + new InputSet('angel_orm_generator', const ['test/models/*.dart']); final PhaseGroup PHASES = new PhaseGroup() ..addPhase(new Phase() diff --git a/tool/watch.dart b/angel_orm_generator/tool/watch.dart similarity index 100% rename from tool/watch.dart rename to angel_orm_generator/tool/watch.dart diff --git a/lib/src/builder/repository.dart b/lib/src/builder/repository.dart deleted file mode 100644 index 73a12445..00000000 --- a/lib/src/builder/repository.dart +++ /dev/null @@ -1,296 +0,0 @@ -import 'dart:async'; -import 'dart:mirrors'; -import 'package:analyzer/dart/element/element.dart'; -import 'package:angel_serialize/angel_serialize.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/source_gen.dart'; -import 'package:query_builder_sql/query_builder_sql.dart'; -import '../annotations.dart'; - -// TODO: whereXLessThan, greaterThan, etc. - -final RegExp _leadingDot = new RegExp(r'^\.+'); - -const List QUERY_DO_NOT_OVERRIDE = const ['when']; - -typedef Iterable SuperArgumentProvider( - ORM model, ClassElement clazz); - -class AngelQueryBuilderGenerator extends GeneratorForAnnotation { - ClassMirror _baseRepositoryClassMirror; - final List _imports = [ - 'dart:async', - 'package:query_builder/query_builder.dart' - ]; - - final Map _constructorParams = {}; - SuperArgumentProvider _superArgProvider; - - AngelQueryBuilderGenerator(Type baseRepositoryQueryClass, - {Iterable additonalImports: const [], - Map constructorParams: const {}, - SuperArgumentProvider superArgProvider}) { - _baseRepositoryClassMirror = reflectClass(baseRepositoryQueryClass); - _imports.addAll(additonalImports ?? []); - _constructorParams.addAll(constructorParams ?? {}); - _superArgProvider = superArgProvider ?? - (annotation, clazz) => [ - literal(annotation.tableName?.isNotEmpty == true - ? annotation.tableName - : pluralize(new ReCase(clazz.name.substring(1)).snakeCase)) - ]; - } - - factory AngelQueryBuilderGenerator.postgresql() => - new AngelQueryBuilderGenerator(SqlRepositoryQuery, constructorParams: { - 'connection': new TypeBuilder('PostgreSQLConnection') - }, additonalImports: [ - 'package:postgres/postgres.dart', - 'package:query_builder_sql/query_builder_sql.dart' - ]); - - @override - Future generateForAnnotatedElement( - Element element, ORM annotation, BuildStep buildStep) async { - if (element.kind != ElementKind.CLASS) - throw 'Only classes may be annotated with @model.'; - var lib = generatePostgresLibrary(element, annotation, buildStep.inputId); - return prettyToSource(lib.buildAst()); - } - - LibraryBuilder generatePostgresLibrary( - ClassElement clazz, ORM annotation, AssetId inputId) { - if (!clazz.name.startsWith('_')) - throw 'Classes annotated with @model must have names starting with an underscore.'; - var lib = new LibraryBuilder(); - lib.addDirectives(_imports.map((p) => new ImportBuilder(p))); - lib.addDirective(new ImportBuilder(p.basename(inputId.path))); - - // Find all aliases... - Map aliases = {}; - clazz.fields.forEach((field) { - var aliasAnnotation = field.metadata - .firstWhere((ann) => matchAnnotation(Alias, ann), orElse: () => null); - if (aliasAnnotation != null) { - var alias = instantiateAnnotation(aliasAnnotation) as Alias; - aliases[field.name] = alias.name; - } - }); - - lib.addMember(generateRepositoryClass(clazz, aliases)); - lib.addMember(generateRepositoryQueryClass(clazz, annotation, aliases)); - return lib; - } - - ClassBuilder generateRepositoryClass( - ClassElement clazz, Map aliases) { - var genClassName = clazz.name.substring(1) + 'Repository'; - var genQueryClassName = genClassName + 'Query'; - var genClass = new ClassBuilder(genClassName); - var genQueryType = new TypeBuilder(genQueryClassName); - - // Add `connection` field + constructor - - var genConstructor = new ConstructorBuilder(); - _constructorParams.forEach((name, type) { - genClass.addField(varFinal(name, type: type)); - genConstructor.addPositional(parameter(name), asField: true); - }); - genClass.addConstructor(genConstructor); - - // Add an all method - genClass.addMethod(new MethodBuilder('all', - returnType: new TypeBuilder(genQueryClassName), - returns: new TypeBuilder(genQueryClassName) - .newInstance([reference('connection')]))); - - // For each field, add a whereX() method... - clazz.fields - .map((field) => generateWhereFieldMethod( - field, reference('all').call([]), genQueryType, aliases)) - .forEach(genClass.addMethod); - return genClass; - } - - ClassBuilder generateRepositoryQueryClass( - ClassElement clazz, ORM annotation, Map aliases) { - var modelClassName = clazz.name.substring(1); - var genClassName = clazz.name.substring(1) + 'RepositoryQuery'; - var genClass = new ClassBuilder(genClassName, - asExtends: new TypeBuilder( - MirrorSystem.getName(_baseRepositoryClassMirror.simpleName), - genericTypes: [new TypeBuilder(modelClassName)])); - var genQueryType = new TypeBuilder(genClassName); - - // Add `connection` field + constructor - - var genConstructor = new ConstructorBuilder( - invokeSuper: _superArgProvider(annotation, clazz)); - _constructorParams.forEach((name, type) { - genClass.addField(varFinal(name, type: type)); - genConstructor.addPositional(parameter(name), asField: true); - }); - genClass.addConstructor(genConstructor); - - // For each field, add a whereX() method... - clazz.fields - .map((field) => generateWhereFieldMethod( - field, explicitThis, genQueryType, aliases)) - .forEach(genClass.addMethod); - - // Add orWhereX() - clazz.fields - .map((f) => generateOrWhereFieldMethod(genQueryType, f)) - .forEach(genClass.addMethod); - - // Override any query methods - _baseRepositoryClassMirror.instanceMembers.forEach((sym, method) { - // Skip setters, etc. - if (!method.isRegularMethod) return; - - // Only if return type contains 'RepositoryQuery' - var methodName = MirrorSystem.getName(sym); - - if (QUERY_DO_NOT_OVERRIDE.contains(methodName)) return; - - var returnTypeName = MirrorSystem.getName(method.returnType.simpleName); - - if (returnTypeName.contains('RepositoryQuery')) { - var overriddenMethod = - new MethodBuilder(methodName, returnType: genQueryType); - // Add @override - overriddenMethod.addAnnotation(lib$core.override); - - // Find all positional and named args - List args = []; - List named = []; - - method.parameters.forEach((param) { - var paramName = MirrorSystem.getName(param.simpleName); - var typeName = MirrorSystem.getName(param.type.simpleName); - var paramType = new TypeBuilder(typeName); - var genParam = parameter(paramName, [paramType]); - - if (!param.isNamed) { - args.add(paramName); - overriddenMethod.addPositional( - param.isOptional ? genParam.asOptional() : genParam); - } else { - overriddenMethod.addNamed(genParam); - named.add(paramName); - } - }); - - // Invoke super - overriddenMethod.addStatement(reference('super') - .invoke(methodName, args.map(reference), - namedArguments: named.fold>( - {}, (out, k) => out..[k] = reference(k))) - .asReturn()); - - genClass.addMethod(overriddenMethod); - } - }); - - // Override toSql to put keys in desired order - // TODO: Override toSql - - // Add get() - genClass.addMethod(generateGetMethod(clazz, modelClassName, aliases)); - - return genClass; - } - - MethodBuilder generateGetMethod( - ClassElement clazz, String modelClassName, Map aliases) { - var meth = new MethodBuilder('get')..addAnnotation(lib$core.override); - - // Map rows to model... - var mapRowsToModel = new MethodBuilder.closure() - ..addPositional(parameter('rows')); - - // First, figure out which rows we fetched... - // - // var requestedKeys = whereFields.keys.isNotEmpty ? whereFields.keys : []; - - //var allModelFields = clazz.fields - // .map((f) => aliases.containsKey(f.name) ? aliases[f.name] : f.name); - //var whereFieldsKeys = reference('whereFields').property('keys'); - - // return new Stream<>.fromFuture(...) - meth.addStatement(lib$async.Stream.newInstance([ - reference('connection') - .invoke('query', [reference('toSql').call([])]).invoke( - 'then', [mapRowsToModel]) - ], constructor: 'fromFuture').asReturn()); - - return meth; - } - - MethodBuilder generateWhereFieldMethod( - FieldElement field, - ExpressionBuilder baseQuery, - TypeBuilder returnType, - Map aliases) { - var rc = new ReCase(field.name); - var whereMethod = - new MethodBuilder('where${rc.pascalCase}', returnType: returnType); - var columnName = - aliases.containsKey(field.name) ? aliases[field.name] : field.name; - whereMethod.addPositional( - parameter(field.name, [new TypeBuilder(field.type.displayName)])); - - if (field.type.name == 'DateTime') { - // Add named `{time: true}` - whereMethod.addNamed( - parameter('time', [lib$core.bool]).asOptional(literal(true))); - // return all().whereDate('x', x, time: time != false); - // return all().where('x', x); - whereMethod.addStatement(baseQuery.invoke('whereDate', [ - literal(columnName), - reference(field.name) - ], namedArguments: { - 'time': reference('time').notEquals(literal(false)) - }).asReturn()); - } else { - // return all().where('x', x); - whereMethod.addStatement(baseQuery.invoke( - 'where', [literal(columnName), reference(field.name)]).asReturn()); - } - - return whereMethod; - } - - MethodBuilder generateOrWhereFieldMethod( - TypeBuilder genQueryClassType, FieldElement field) { - var rc = new ReCase(field.name); - var orWhereMethod = new MethodBuilder('orWhere' + rc.pascalCase, - returnType: genQueryClassType); - orWhereMethod.addPositional( - parameter(field.name, [new TypeBuilder(field.type.displayName)])); - - if (field.type.name == 'DateTime') { - orWhereMethod.addNamed(parameter('time', [lib$core.bool])); - orWhereMethod.addStatement(reference('or').call([ - reference('where' + rc.pascalCase).call([ - reference(field.name) - ], namedArguments: { - 'time': reference('time').notEquals(literal(false)) - }) - ]).asReturn()); - } else { - orWhereMethod.addStatement(reference('or').call([ - reference('where' + rc.pascalCase).call([reference(field.name)]) - ]).asReturn()); - } - - return orWhereMethod; - } -} diff --git a/pubspec.yaml b/pubspec.yaml deleted file mode 100644 index ea264c71..00000000 --- a/pubspec.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: angel_orm -version: 0.0.0 -description: Source-generated ORM for use with the Angel framework. -author: Tobe O -homepage: https://github.com/angel-dart/angel_mongo -dependencies: - angel_framework: ">=1.0.0-dev < 2.0.0" - code_builder: ^1.0.0 - id: ^1.0.0 - inflection: ^0.4.1 - intl: ^0.15.1 - pool: ^1.0.0 - postgres: ">=0.9.5 <1.0.0" - query_builder_sql: ^1.0.0-alpha - recase: ^1.0.0 - source_gen: ^0.6.0 -dev_dependencies: - angel_diagnostics: ">=1.0.0 <2.0.0" - angel_serialize: - path: ../serialize - angel_test: ">=1.0.0 <2.0.0" - build_runner: ^0.3.0 - http: ">= 0.11.3 < 0.12.0" - test: ">= 0.12.13 < 0.13.0" \ No newline at end of file diff --git a/tool/.travis.sh b/tool/.travis.sh new file mode 100644 index 00000000..ad6b1a93 --- /dev/null +++ b/tool/.travis.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +cd angel_orm_generator +pub get +dart tool/build.dart +POSTGRES_USERNAME="angel_orm" POSTGRES_PASSWORD="angel_orm" pub run test \ No newline at end of file