All static methods working...

This commit is contained in:
thosakwe 2017-07-09 12:53:35 -04:00
parent 2ecb60f16e
commit 2d6ec2ed17
16 changed files with 468 additions and 189 deletions

View file

@ -5,16 +5,14 @@
**This project is currently in the early stages, and may change at any given
time without warning.**
Source-generated ORM for use with the Angel framework. Documentation is coming soon.
This ORM can work with virtually any database, thanks to the functionality exposed by
`package:query_builder`.
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
* MongoDB (planned)
* RethinkDB (planned)
* In-Memory (planned)
# Models
Your model, courtesy of `package:angel_serialize`:
```dart
@ -35,11 +33,12 @@ class _Car extends Model {
}
```
Models can still use the `@Alias()` annotation. `package:angel_orm` obeys it.
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.
You can run complex queries like:
MVC just got a whole lot easier:
```dart
import 'package:angel_framework/angel_framework.dart';

View file

@ -96,7 +96,7 @@ PostgresBuildContext buildContext(
column = const Column(type: ColumnType.NUMERIC);
break;
case 'bool':
column = const Column(type: ColumnType.BIT);
column = const Column(type: ColumnType.BOOLEAN);
break;
case 'DateTime':
column = const Column(type: ColumnType.TIME_STAMP);

View file

@ -19,14 +19,19 @@ import 'postgres_build_context.dart';
// TODO: HasOne, HasMany, BelongsTo
class SQLMigrationGenerator implements Builder {
/// If "true" (default), then field names will automatically be (de)serialized as snake_case.
/// If `true` (default), then field names will automatically be (de)serialized as snake_case.
final bool autoSnakeCaseNames;
/// If "true" (default), then
/// 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.autoSnakeCaseNames: true,
this.autoIdAndDateFields: true,
this.temporary: false});
@override
Map<String, List<String>> get buildExtensions => {
@ -80,7 +85,10 @@ class SQLMigrationGenerator implements Builder {
}
void buildUpMigration(PostgresBuildContext ctx, StringBuffer buf) {
buf.writeln('CREATE TABLE "${ctx.tableName}" (');
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) {

View file

@ -17,6 +17,8 @@ import 'package:angel_serialize/src/find_annotation.dart';
import 'build_context.dart';
import 'postgres_build_context.dart';
const List<String> RELATIONS = const ['and', 'or', 'not'];
// TODO: HasOne, HasMany, BelongsTo
class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
/// If "true" (default), then field names will automatically be (de)serialized as snake_case.
@ -102,7 +104,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
var connection = reference('connection');
// Add or + not
for (var relation in ['and', 'or', 'not']) {
for (var relation in RELATIONS) {
clazz.addField(varFinal('_$relation',
type: new TypeBuilder('List', genericTypes: [lib$core.String]),
value: list([])));
@ -112,8 +114,9 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
parameter('other', [new TypeBuilder(ctx.queryClassName)]));
var otherWhere = reference('other').property('where');
var compiled = reference('compiled');
relationMethod.addStatement(
varField('compiled', value: otherWhere.invoke('toWhereClause', [])));
relationMethod.addStatement(varField('compiled',
value: otherWhere.invoke('toWhereClause', [],
namedArguments: {'keyword': literal(false)})));
relationMethod.addStatement(ifThen(compiled.notEquals(literal(null)), [
reference('_$relation').invoke('add', [compiled])
]));
@ -142,9 +145,12 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
// Add update()...
clazz.addMethod(buildUpdateMethod(ctx));
// Add remove()...
// Add delete()...
clazz.addMethod(buildDeleteMethod(ctx));
// Add deleteOne()...
clazz.addMethod(buildDeleteOneMethod(ctx), asStatic: true);
// Add insert()...
clazz.addMethod(buildInsertMethod(ctx), asStatic: true);
@ -176,6 +182,16 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
buf.invoke('write', [literal(' ') + whereClause])
]));
for (var relation in RELATIONS) {
var ref = reference('_$relation');
var upper = relation.toUpperCase();
var joined = ref.invoke('join', [literal(',')]);
meth.addStatement(ifThen(ref.property('isNotEmpty'), [
buf.invoke('write', [literal(' $upper (') + joined + literal(')')])
]));
}
meth.addStatement(buf.invoke('write', [literal(';')]));
meth.addStatement(buf.invoke('toString', []).asReturn());
@ -200,14 +216,18 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
var name = ctx.resolveFieldName(field.name);
var rowKey = row[literal(i++)];
if (field.type.isAssignableTo(ctx.dateTimeType)) {
/* if (field.type.isAssignableTo(ctx.dateTimeType)) {
// TODO: Handle DATE and not just DATETIME
data[name] = DATE_YMD_HMS.invoke('parse', [rowKey]);
} else if (field.name == 'id' && ctx.shimmed.containsKey('id')) {
data[name] = rowKey.invoke('toString', []);
} else if (field.type.isAssignableTo(ctx.typeProvider.boolType)) {
data[name] = rowKey.equals(literal(1));
} else
*/
if (field.name == 'id' && ctx.shimmed.containsKey('id')) {
data[name] = rowKey.invoke('toString', []);
} /* else if (field.type.isAssignableTo(ctx.typeProvider.boolType)) {
// TODO: Find out what date is returned as
data[name] = rowKey.equals(literal(1));
}*/
else
data[name] = rowKey;
});
@ -229,20 +249,9 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
return meth;
}
MethodBuilder buildGetMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('get',
returnType: new TypeBuilder('Stream',
genericTypes: [new TypeBuilder(ctx.modelClassName)]));
meth.addPositional(
parameter('connection', [new TypeBuilder('PostgreSQLConnection')]));
var streamController = new TypeBuilder('StreamController',
genericTypes: [new TypeBuilder(ctx.modelClassName)]);
var ctrl = reference('ctrl'), connection = reference('connection');
meth.addStatement(varField('ctrl',
type: streamController, value: streamController.newInstance([])));
void _invokeStreamClosure(ExpressionBuilder future, MethodBuilder meth) {
var ctrl = reference('ctrl');
// Invoke query...
var future = connection.invoke('query', [reference('toSql').call([])]);
var catchError = ctrl.property('addError');
var then = new MethodBuilder.closure()..addPositional(parameter('rows'));
then.addStatement(reference('rows')
@ -252,6 +261,22 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
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: [new TypeBuilder(ctx.modelClassName)]));
meth.addPositional(
parameter('connection', [new TypeBuilder('PostgreSQLConnection')]));
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', [reference('toSql').call([])]);
_invokeStreamClosure(future, meth);
return meth;
}
@ -324,26 +349,18 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
return substitutionValues;
}
void _executeQuery(StringBuffer buf, MethodBuilder meth,
Map<String, ExpressionBuilder> substitutionValues) {
ExpressionBuilder _executeQuery(ExpressionBuilder queryString,
MethodBuilder meth, Map<String, ExpressionBuilder> substitutionValues) {
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]).asReturn());
var query = queryString;
return connection.invoke('query', [query],
namedArguments: {'substitutionValues': map(substitutionValues)});
}
MethodBuilder buildUpdateMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('update',
modifier: MethodModifier.asAsync,
returnType: new TypeBuilder('Future',
returnType: new TypeBuilder('Stream',
genericTypes: [new TypeBuilder(ctx.modelClassName)]));
meth.addPositional(parameter('id', [lib$core.int]));
meth.addPositional(
parameter('connection', [new TypeBuilder('PostgreSQLConnection')]));
_addAllNamed(meth, ctx);
@ -369,18 +386,49 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
buf.write('@${field.name}');
}
});
buf.write(') WHERE "id" = @id');
buf.write(') ');
_addReturning(buf, ctx);
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.equals(literal(null)), [
$buf.invoke('write', [literal('WHERE "id" = @id')]),
elseThen([
$buf.invoke('write', [whereClause])
])
]));
var buf2 = new StringBuffer();
_addReturning(buf2, ctx);
_ensureDates(meth, ctx);
var substitutionValues = _buildSubstitutionValues(ctx);
substitutionValues.putIfAbsent('id', () => reference('id'));
_executeQuery(buf, meth, substitutionValues);
var ctrlType = new TypeBuilder('StreamController',
genericTypes: [new TypeBuilder(ctx.modelClassName)]);
meth.addStatement(varField('ctrl', value: ctrlType.newInstance([])));
var result = _executeQuery(
$buf.invoke('toString', []) + literal(buf2.toString()),
meth,
substitutionValues);
_invokeStreamClosure(result, meth);
return meth;
}
MethodBuilder buildDeleteMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('delete',
returnType: new TypeBuilder('Stream',
genericTypes: [new TypeBuilder(ctx.modelClassName)]));
meth.addPositional(
parameter('connection', [new TypeBuilder('PostgreSQLConnection')]));
return meth;
}
MethodBuilder buildDeleteOneMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('deleteOne',
modifier: MethodModifier.asAsync,
returnType: new TypeBuilder('Future',
genericTypes: [new TypeBuilder(ctx.modelClassName)]))
@ -401,7 +449,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
// await connection.execute('...');
meth.addStatement(varField('result',
value: connection.invoke('execute', [
literal('DELETE FROM "${ctx.tableName}" WHERE id = @id LIMIT 1;')
literal('DELETE FROM "${ctx.tableName}" WHERE id = @id;')
], namedArguments: {
'substitutionValues': map({'id': id})
}).asAwait()));
@ -453,15 +501,43 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
}
});
buf.write(')');
_addReturning(buf, ctx);
buf.write(');');
// meth.addStatement(lib$core.print.call([literal(buf.toString())]));
_ensureDates(meth, ctx);
var substitutionValues = _buildSubstitutionValues(ctx);
_executeQuery(buf, meth, substitutionValues);
// connection.execute...
var connection = reference('connection'), nRows = reference('nRows');
meth.addStatement(varField('nRows',
value: connection.invoke('execute', [
literal(buf.toString())
], namedArguments: {
'substitutionValues': map(substitutionValues)
}).asAwait()));
meth.addStatement(ifThen(nRows < literal(1), [
lib$core.StateError.newInstance([
literal('Insertion into "${ctx.tableName}" table failed.')
]).asThrow()
]));
// 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]);
*/
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;
}
@ -472,28 +548,33 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
TypeBuilder queryBuilderType;
List<ExpressionBuilder> args = [];
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.resolveFieldName(field.name)));
break;
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.resolveFieldName(field.name)));
break;
}
}
if (queryBuilderType == null)
@ -505,6 +586,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
// Create "toWhereClause()"
var toWhereClause =
new MethodBuilder('toWhereClause', returnType: lib$core.String);
toWhereClause.addNamed(parameter('keyword', [lib$core.bool]));
// List<String> expressions = [];
toWhereClause.addStatement(varFinal('expressions',
@ -525,13 +607,16 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
]));
});
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),
(literal('WHERE ') + expressions.invoke('join', [literal(' AND ')]))
.parentheses())
.ternary(literal(null),
(kw + expressions.invoke('join', [literal(' AND ')])).parentheses())
.asReturn());
clazz.addMethod(toWhereClause);

View file

@ -220,9 +220,9 @@ class AngelQueryBuilderGenerator extends GeneratorForAnnotation<ORM> {
//
// var requestedKeys = whereFields.keys.isNotEmpty ? whereFields.keys : [<all fields...>];
var allModelFields = clazz.fields
.map((f) => aliases.containsKey(f.name) ? aliases[f.name] : f.name);
var whereFieldsKeys = reference('whereFields').property('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([

View file

@ -58,6 +58,8 @@ class ColumnType {
final String name;
const ColumnType._(this.name);
static const ColumnType BOOLEAN = const ColumnType._('boolean');
static const ColumnType SMALL_SERIAL = const ColumnType._('smallserial');
static const ColumnType SERIAL = const ColumnType._('serial');
static const ColumnType BIG_SERIAL = const ColumnType._('bigserial');

View file

@ -1,6 +1,5 @@
import 'package:intl/intl.dart';
final DateFormat DATE_YMD = new DateFormat('yyyy-MM-dd');
final DateFormat DATE_YMD_HMS = new DateFormat('yyyy-MM-dd HH:mm:ss');
@ -78,7 +77,8 @@ class StringSqlExpressionBuilder implements SqlExpressionBuilder {
@override
String compile() {
if (_value == null) return null;
return '$_op `$_value`';
var v = _value.replaceAll("'", "\\'");
return "$_op '$v'";
}
void isEmpty() => equals('');
@ -113,7 +113,7 @@ class BooleanSqlExpressionBuilder implements SqlExpressionBuilder {
@override
String compile() {
if (_value == null) return null;
var v = _value ? 1 : 0;
var v = _value ? 'TRUE' : 'FALSE';
return '$_op $v';
}
@ -151,7 +151,7 @@ class DateTimeSqlExpressionBuilder implements SqlExpressionBuilder {
bool _change(String _op, DateTime dt, bool time) {
var dateString = time ? DATE_YMD_HMS.format(dt) : DATE_YMD.format(dt);
_raw = '`$columnName` $_op \'$dateString\'';
_raw = '"$columnName" $_op \'$dateString\'';
return true;
}
@ -184,12 +184,12 @@ class DateTimeSqlExpressionBuilder implements SqlExpressionBuilder {
String compile() {
if (_raw?.isNotEmpty == true) return _raw;
List<String> parts = [];
if (year.hasValue) parts.add('YEAR(`$columnName`) ${year.compile()}');
if (month.hasValue) parts.add('MONTH(`$columnName`) ${month.compile()}');
if (day.hasValue) parts.add('DAY(`$columnName`) ${day.compile()}');
if (hour.hasValue) parts.add('HOUR(`$columnName`) ${hour.compile()}');
if (minute.hasValue) parts.add('MINUTE(`$columnName`) ${minute.compile()}');
if (second.hasValue) parts.add('SECOND(`$columnName`) ${second.compile()}');
if (year.hasValue) parts.add('YEAR("$columnName") ${year.compile()}');
if (month.hasValue) parts.add('MONTH("$columnName") ${month.compile()}');
if (day.hasValue) parts.add('DAY("$columnName") ${day.compile()}');
if (hour.hasValue) parts.add('HOUR("$columnName") ${hour.compile()}');
if (minute.hasValue) parts.add('MINUTE("$columnName") ${minute.compile()}');
if (second.hasValue) parts.add('SECOND("$columnName") ${second.compile()}');
return parts.isEmpty ? null : parts.join(' AND ');
}

View file

@ -4,27 +4,11 @@ import 'package:postgres/postgres.dart';
import 'package:test/test.dart';
import 'models/car.dart';
import 'models/car.orm.g.dart';
import 'common.dart';
final DateTime MILENNIUM = new DateTime.utc(2000, 1, 1);
main() {
PostgreSQLConnection connection;
setUp(() async {
connection = new PostgreSQLConnection('127.0.0.1', 0, '');
await connection.open();
// Create temp table
var query = await new File('test/models/car.sql').readAsString();
await connection.execute(query);
});
tearDown(() async {
// Drop `cars`
await connection.execute('DROP TABLE `cars`;');
await connection.close();
});
test('to where', () {
var query = new CarQuery();
query.where
@ -33,7 +17,7 @@ main() {
var whereClause = query.where.toWhereClause();
print('Where clause: $whereClause');
expect(whereClause,
"WHERE `family_friendly` = 1 AND `recalled_at` <= '00-01-01'");
'WHERE "family_friendly" = TRUE AND "recalled_at" <= \'2000-01-01\'');
});
test('parseRow', () {
@ -41,7 +25,7 @@ main() {
0,
'Mazda',
'CX9',
1,
true,
DATE_YMD_HMS.format(MILENNIUM),
DATE_YMD_HMS.format(MILENNIUM),
DATE_YMD_HMS.format(MILENNIUM)
@ -61,5 +45,89 @@ main() {
startsWith(car.updatedAt.toIso8601String()));
});
test('insert', () async {});
group('queries', () {
PostgreSQLConnection connection;
setUp(() async {
connection = await connectToPostgres();
});
group('selects', () {
test('select all', () async {
var cars = await CarQuery.getAll(connection).toList();
expect(cars, []);
});
group('with data', () {
Car ferrari;
setUp(() async {
ferrari = await CarQuery.insert(connection,
make: 'Ferrari',
description: 'Vroom vroom!',
familyFriendly: false);
});
test('where clause is applied', () async {
var query = new CarQuery()..where.familyFriendly.equals(true);
var cars = await query.get(connection).toList();
expect(cars, isEmpty);
var sportsCars = new CarQuery()..where.familyFriendly.notEquals(true);
cars = await sportsCars.get(connection).toList();
print(cars.map((c) => c.toJson()).toList());
var car = cars.first;
expect(car.make, ferrari.make);
expect(car.description, ferrari.description);
expect(car.familyFriendly, ferrari.familyFriendly);
expect(car.recalledAt, isNull);
});
test('and clause', () async {
var query = new CarQuery()
..where.make.like('Fer%')
..and(new CarQuery()..where.familyFriendly.equals(true));
print(query.toSql());
var cars = await query.get(connection).toList();
expect(cars, isEmpty);
});
test('get one', () async {
var car = await CarQuery.getOne(int.parse(ferrari.id), connection);
expect(car.toJson(), ferrari.toJson());
});
test('delete one', () async {
var car = await CarQuery.deleteOne(int.parse(ferrari.id), connection);
expect(car.toJson(), ferrari.toJson());
var cars = await CarQuery.getAll(connection).toList();
expect(cars, isEmpty);
});
test('delete', () async {
var query = new CarQuery();
var cars = await query.delete(connection).toList();
expect(cars, hasLength(1));
expect(cars.first.toJson(), ferrari.toJson());
});
});
});
test('insert', () async {
var recalledAt = new DateTime.now();
var car = await CarQuery.insert(connection,
make: 'Honda',
description: 'Hello',
familyFriendly: true,
recalledAt: recalledAt);
expect(car.id, isNotNull);
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(car.createdAt, allOf(isNotNull, equals(car.updatedAt)));
});
});
}

15
test/common.dart Normal file
View file

@ -0,0 +1,15 @@
import 'dart:async';
import 'dart:io';
import 'package:postgres/postgres.dart';
Future<PostgreSQLConnection> connectToPostgres() async {
var conn = new PostgreSQLConnection('127.0.0.1', 5432, 'angel_orm_test',
username: Platform.environment['POSTGRES_USERNAME'] ?? 'postgres',
password: Platform.environment['POSTGRES_PASSWORD'] ?? 'password');
await conn.open();
var query = await new File('test/models/car.up.g.sql').readAsString();
await conn.execute(query);
return conn;
}

View file

@ -20,21 +20,21 @@ class AuthorQuery {
final AuthorQueryWhere where = new AuthorQueryWhere();
void and(AuthorQuery other) {
var compiled = other.where.toWhereClause();
var compiled = other.where.toWhereClause(keyword: false);
if (compiled != null) {
_and.add(compiled);
}
}
void or(AuthorQuery other) {
var compiled = other.where.toWhereClause();
var compiled = other.where.toWhereClause(keyword: false);
if (compiled != null) {
_or.add(compiled);
}
}
void not(AuthorQuery other) {
var compiled = other.where.toWhereClause();
var compiled = other.where.toWhereClause(keyword: false);
if (compiled != null) {
_not.add(compiled);
}
@ -46,6 +46,15 @@ class AuthorQuery {
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(';');
return buf.toString();
}
@ -54,8 +63,8 @@ class AuthorQuery {
return new Author.fromJson({
'id': row[0].toString(),
'name': row[1],
'created_at': DATE_YMD_HMS.parse(row[2]),
'updated_at': DATE_YMD_HMS.parse(row[3])
'created_at': row[2],
'updated_at': row[3]
});
}
@ -73,24 +82,39 @@ class AuthorQuery {
substitutionValues: {'id': id}).then((rows) => parseRow(rows.first));
}
Future<Author> update(int id, PostgreSQLConnection connection,
{String name, DateTime createdAt, DateTime updatedAt}) async {
Stream<Author> update(PostgreSQLConnection connection,
{String name, DateTime createdAt, DateTime updatedAt}) {
var buf = new StringBuffer(
'UPDATE "authors" SET ("name", "created_at", "updated_at") = (@name, @createdAt, @updatedAt) ');
var whereClause = where.toWhereClause();
if (whereClause == null) {
buf.write('WHERE "id" = @id');
} else {
buf.write(whereClause);
}
var __ormNow__ = new DateTime.now();
var result = await connection.query(
'UPDATE "authors" SET ("name", "created_at", "updated_at") = (@name, @createdAt, @updatedAt) WHERE "id" = @id RETURNING ("id", "name", "created_at", "updated_at");',
var ctrl = new StreamController<Author>();
connection.query(
buf.toString() +
' RETURNING ("id", "name", "created_at", "updated_at");',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__,
'id': id
});
return parseRow(result);
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
}).then((rows) {
rows.map(parseRow).forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
Future<Author> delete(int id, PostgreSQLConnection connection) async {
Stream<Author> delete(PostgreSQLConnection connection) async {}
static Future<Author> deleteOne(
int id, PostgreSQLConnection connection) async {
var __ormBeforeDelete__ = await AuthorQuery.getOne(id, connection);
var result = await connection.execute(
'DELETE FROM "authors" WHERE id = @id LIMIT 1;',
'DELETE FROM "authors" WHERE id = @id;',
substitutionValues: {'id': id});
if (result != 1) {
new StateError('DELETE query deleted ' +
@ -103,14 +127,19 @@ class AuthorQuery {
static Future<Author> insert(PostgreSQLConnection connection,
{String name, DateTime createdAt, DateTime updatedAt}) async {
var __ormNow__ = new DateTime.now();
var result = await connection.query(
'INSERT INTO "authors" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt) RETURNING ("id", "name", "created_at", "updated_at");',
var nRows = await connection.execute(
'INSERT INTO "authors" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt);',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
});
return parseRow(result);
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]);
}
static Stream<Author> getAll(PostgreSQLConnection connection) =>
@ -118,7 +147,8 @@ class AuthorQuery {
}
class AuthorQueryWhere {
final StringSqlExpressionBuilder id = new StringSqlExpressionBuilder();
final NumericSqlExpressionBuilder<int> id =
new NumericSqlExpressionBuilder<int>();
final StringSqlExpressionBuilder name = new StringSqlExpressionBuilder();
@ -128,7 +158,7 @@ class AuthorQueryWhere {
final DateTimeSqlExpressionBuilder updatedAt =
new DateTimeSqlExpressionBuilder('updated_at');
String toWhereClause() {
String toWhereClause({bool keyword}) {
final List<String> expressions = [];
if (id.hasValue) {
expressions.add('"id" ' + id.compile());
@ -142,6 +172,8 @@ class AuthorQueryWhere {
if (updatedAt.hasValue) {
expressions.add(updatedAt.compile());
}
return expressions.isEmpty ? null : ('WHERE ' + expressions.join(' AND '));
return expressions.isEmpty
? null
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
}
}

View file

@ -1,4 +1,4 @@
CREATE TABLE "authors" (
CREATE TEMPORARY TABLE "authors" (
"id" serial,
"name" varchar,
"created_at" timestamp,

View file

@ -21,21 +21,21 @@ class BookQuery {
final BookQueryWhere where = new BookQueryWhere();
void and(BookQuery other) {
var compiled = other.where.toWhereClause();
var compiled = other.where.toWhereClause(keyword: false);
if (compiled != null) {
_and.add(compiled);
}
}
void or(BookQuery other) {
var compiled = other.where.toWhereClause();
var compiled = other.where.toWhereClause(keyword: false);
if (compiled != null) {
_or.add(compiled);
}
}
void not(BookQuery other) {
var compiled = other.where.toWhereClause();
var compiled = other.where.toWhereClause(keyword: false);
if (compiled != null) {
_not.add(compiled);
}
@ -47,6 +47,15 @@ class BookQuery {
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(';');
return buf.toString();
}
@ -55,8 +64,8 @@ class BookQuery {
return new Book.fromJson({
'id': row[0].toString(),
'name': row[1],
'created_at': DATE_YMD_HMS.parse(row[2]),
'updated_at': DATE_YMD_HMS.parse(row[3]),
'created_at': row[2],
'updated_at': row[3],
'author': row.length < 5 ? null : AuthorQuery.parseRow(row[4])
});
}
@ -75,24 +84,37 @@ class BookQuery {
substitutionValues: {'id': id}).then((rows) => parseRow(rows.first));
}
Future<Book> update(int id, PostgreSQLConnection connection,
{String name, DateTime createdAt, DateTime updatedAt}) async {
Stream<Book> update(PostgreSQLConnection connection,
{String name, DateTime createdAt, DateTime updatedAt}) {
var buf = new StringBuffer(
'UPDATE "books" SET ("name", "created_at", "updated_at") = (@name, @createdAt, @updatedAt) ');
var whereClause = where.toWhereClause();
if (whereClause == null) {
buf.write('WHERE "id" = @id');
} else {
buf.write(whereClause);
}
var __ormNow__ = new DateTime.now();
var result = await connection.query(
'UPDATE "books" SET ("name", "created_at", "updated_at") = (@name, @createdAt, @updatedAt) WHERE "id" = @id RETURNING ("id", "name", "created_at", "updated_at");',
var ctrl = new StreamController<Book>();
connection.query(
buf.toString() +
' RETURNING ("id", "name", "created_at", "updated_at");',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__,
'id': id
});
return parseRow(result);
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
}).then((rows) {
rows.map(parseRow).forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
Future<Book> delete(int id, PostgreSQLConnection connection) async {
Stream<Book> delete(PostgreSQLConnection connection) async {}
static Future<Book> deleteOne(int id, PostgreSQLConnection connection) async {
var __ormBeforeDelete__ = await BookQuery.getOne(id, connection);
var result = await connection.execute(
'DELETE FROM "books" WHERE id = @id LIMIT 1;',
var result = await connection.execute('DELETE FROM "books" WHERE id = @id;',
substitutionValues: {'id': id});
if (result != 1) {
new StateError('DELETE query deleted ' +
@ -105,14 +127,19 @@ class BookQuery {
static Future<Book> insert(PostgreSQLConnection connection,
{String name, DateTime createdAt, DateTime updatedAt}) async {
var __ormNow__ = new DateTime.now();
var result = await connection.query(
'INSERT INTO "books" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt) RETURNING ("id", "name", "created_at", "updated_at");',
var nRows = await connection.execute(
'INSERT INTO "books" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt);',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
});
return parseRow(result);
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]);
}
static Stream<Book> getAll(PostgreSQLConnection connection) =>
@ -120,7 +147,8 @@ class BookQuery {
}
class BookQueryWhere {
final StringSqlExpressionBuilder id = new StringSqlExpressionBuilder();
final NumericSqlExpressionBuilder<int> id =
new NumericSqlExpressionBuilder<int>();
final StringSqlExpressionBuilder name = new StringSqlExpressionBuilder();
@ -130,7 +158,7 @@ class BookQueryWhere {
final DateTimeSqlExpressionBuilder updatedAt =
new DateTimeSqlExpressionBuilder('updated_at');
String toWhereClause() {
String toWhereClause({bool keyword}) {
final List<String> expressions = [];
if (id.hasValue) {
expressions.add('"id" ' + id.compile());
@ -144,6 +172,8 @@ class BookQueryWhere {
if (updatedAt.hasValue) {
expressions.add(updatedAt.compile());
}
return expressions.isEmpty ? null : ('WHERE ' + expressions.join(' AND '));
return expressions.isEmpty
? null
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
}
}

View file

@ -1,4 +1,4 @@
CREATE TABLE "books" (
CREATE TEMPORARY TABLE "books" (
"id" serial,
"name" varchar,
"created_at" timestamp,

View file

@ -20,21 +20,21 @@ class CarQuery {
final CarQueryWhere where = new CarQueryWhere();
void and(CarQuery other) {
var compiled = other.where.toWhereClause();
var compiled = other.where.toWhereClause(keyword: false);
if (compiled != null) {
_and.add(compiled);
}
}
void or(CarQuery other) {
var compiled = other.where.toWhereClause();
var compiled = other.where.toWhereClause(keyword: false);
if (compiled != null) {
_or.add(compiled);
}
}
void not(CarQuery other) {
var compiled = other.where.toWhereClause();
var compiled = other.where.toWhereClause(keyword: false);
if (compiled != null) {
_not.add(compiled);
}
@ -46,6 +46,15 @@ class CarQuery {
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(';');
return buf.toString();
}
@ -55,10 +64,10 @@ class CarQuery {
'id': row[0].toString(),
'make': row[1],
'description': row[2],
'family_friendly': row[3] == 1,
'recalled_at': DATE_YMD_HMS.parse(row[4]),
'created_at': DATE_YMD_HMS.parse(row[5]),
'updated_at': DATE_YMD_HMS.parse(row[6])
'family_friendly': row[3],
'recalled_at': row[4],
'created_at': row[5],
'updated_at': row[6]
});
}
@ -76,32 +85,54 @@ class CarQuery {
substitutionValues: {'id': id}).then((rows) => parseRow(rows.first));
}
Future<Car> update(int id, PostgreSQLConnection connection,
Stream<Car> update(PostgreSQLConnection connection,
{String make,
String description,
bool familyFriendly,
DateTime recalledAt,
DateTime createdAt,
DateTime updatedAt}) async {
DateTime updatedAt}) {
var buf = new StringBuffer(
'UPDATE "cars" SET ("make", "description", "family_friendly", "recalled_at", "created_at", "updated_at") = (@make, @description, @familyFriendly, @recalledAt, @createdAt, @updatedAt) ');
var whereClause = where.toWhereClause();
if (whereClause == null) {
buf.write('WHERE "id" = @id');
} else {
buf.write(whereClause);
}
var __ormNow__ = new DateTime.now();
var result = await connection.query(
'UPDATE "cars" SET ("make", "description", "family_friendly", "recalled_at", "created_at", "updated_at") = (@make, @description, @familyFriendly, @recalledAt, @createdAt, @updatedAt) WHERE "id" = @id RETURNING ("id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at");',
var ctrl = new StreamController<Car>();
connection.query(
buf.toString() +
' RETURNING ("id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at");',
substitutionValues: {
'make': make,
'description': description,
'familyFriendly': familyFriendly,
'recalledAt': recalledAt,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__,
'id': id
});
return parseRow(result);
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
}).then((rows) {
rows.map(parseRow).forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
Future<Car> delete(int id, PostgreSQLConnection connection) async {
Stream<Car> delete(PostgreSQLConnection connection) {
var query = 'DELETE FROM "cars" RETURNING ("id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at");';
StreamController<Car> ctrl = new StreamController<Car>();
connection.execute(query).then((rows) {
print('Rows: $rows');
rows.map(parseRow).forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
static Future<Car> deleteOne(int id, PostgreSQLConnection connection) async {
var __ormBeforeDelete__ = await CarQuery.getOne(id, connection);
var result = await connection.execute(
'DELETE FROM "cars" WHERE id = @id LIMIT 1;',
var result = await connection.execute('DELETE FROM "cars" WHERE id = @id;',
substitutionValues: {'id': id});
if (result != 1) {
new StateError('DELETE query deleted ' +
@ -119,8 +150,8 @@ class CarQuery {
DateTime createdAt,
DateTime updatedAt}) async {
var __ormNow__ = new DateTime.now();
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");',
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);',
substitutionValues: {
'make': make,
'description': description,
@ -129,7 +160,12 @@ class CarQuery {
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
});
return parseRow(result);
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]);
}
static Stream<Car> getAll(PostgreSQLConnection connection) =>
@ -137,7 +173,8 @@ class CarQuery {
}
class CarQueryWhere {
final StringSqlExpressionBuilder id = new StringSqlExpressionBuilder();
final NumericSqlExpressionBuilder<int> id =
new NumericSqlExpressionBuilder<int>();
final StringSqlExpressionBuilder make = new StringSqlExpressionBuilder();
@ -156,7 +193,7 @@ class CarQueryWhere {
final DateTimeSqlExpressionBuilder updatedAt =
new DateTimeSqlExpressionBuilder('updated_at');
String toWhereClause() {
String toWhereClause({bool keyword}) {
final List<String> expressions = [];
if (id.hasValue) {
expressions.add('"id" ' + id.compile());
@ -179,6 +216,8 @@ class CarQueryWhere {
if (updatedAt.hasValue) {
expressions.add(updatedAt.compile());
}
return expressions.isEmpty ? null : ('WHERE ' + expressions.join(' AND '));
return expressions.isEmpty
? null
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
}
}

View file

@ -1,8 +1,8 @@
CREATE TABLE "cars" (
CREATE TEMPORARY TABLE "cars" (
"id" serial,
"make" varchar,
"description" varchar,
"family_friendly" bit,
"family_friendly" boolean,
"recalled_at" timestamp,
"created_at" timestamp,
"updated_at" timestamp

View file

@ -13,4 +13,5 @@ final PhaseGroup PHASES = new PhaseGroup()
new GeneratorBuilder([new PostgresORMGenerator()],
isStandalone: true, generatedExtension: '.orm.g.dart'),
MODELS))
..addPhase(new Phase()..addAction(new SQLMigrationGenerator(), MODELS));
..addPhase(new Phase()
..addAction(new SQLMigrationGenerator(temporary: true), MODELS));