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 **This project is currently in the early stages, and may change at any given
time without warning.** time without warning.**
Source-generated ORM for use with the Angel framework. Documentation is coming soon. Source-generated PostgreSQL ORM for use with the
This ORM can work with virtually any database, thanks to the functionality exposed by [Angel framework](https://angel-dart.github.io).
`package:query_builder`. Now you can combine the power and flexibility of Angel with a strongly-typed ORM.
Currently supported: Currently supported:
* PostgreSQL * PostgreSQL
* MongoDB (planned)
* RethinkDB (planned)
* In-Memory (planned)
# Models
Your model, courtesy of `package:angel_serialize`: Your model, courtesy of `package:angel_serialize`:
```dart ```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 After building, you'll have access to a `Query` class with strongly-typed methods that
allow to run asynchronous queries without a headache. allow to run asynchronous queries without a headache.
You can run complex queries like:
MVC just got a whole lot easier:
```dart ```dart
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';

View file

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

View file

@ -19,14 +19,19 @@ import 'postgres_build_context.dart';
// TODO: HasOne, HasMany, BelongsTo // TODO: HasOne, HasMany, BelongsTo
class SQLMigrationGenerator implements Builder { 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; 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; final bool autoIdAndDateFields;
/// If `true` (default: `false`), then the resulting schema will generate a `TEMPORARY` table.
final bool temporary;
const SQLMigrationGenerator( const SQLMigrationGenerator(
{this.autoSnakeCaseNames: true, this.autoIdAndDateFields: true}); {this.autoSnakeCaseNames: true,
this.autoIdAndDateFields: true,
this.temporary: false});
@override @override
Map<String, List<String>> get buildExtensions => { Map<String, List<String>> get buildExtensions => {
@ -80,7 +85,10 @@ class SQLMigrationGenerator implements Builder {
} }
void buildUpMigration(PostgresBuildContext ctx, StringBuffer buf) { 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; int i = 0;
ctx.columnInfo.forEach((name, col) { ctx.columnInfo.forEach((name, col) {

View file

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

View file

@ -220,9 +220,9 @@ class AngelQueryBuilderGenerator extends GeneratorForAnnotation<ORM> {
// //
// var requestedKeys = whereFields.keys.isNotEmpty ? whereFields.keys : [<all fields...>]; // var requestedKeys = whereFields.keys.isNotEmpty ? whereFields.keys : [<all fields...>];
var allModelFields = clazz.fields //var allModelFields = clazz.fields
.map((f) => aliases.containsKey(f.name) ? aliases[f.name] : f.name); // .map((f) => aliases.containsKey(f.name) ? aliases[f.name] : f.name);
var whereFieldsKeys = reference('whereFields').property('keys'); //var whereFieldsKeys = reference('whereFields').property('keys');
// return new Stream<>.fromFuture(...) // return new Stream<>.fromFuture(...)
meth.addStatement(lib$async.Stream.newInstance([ meth.addStatement(lib$async.Stream.newInstance([

View file

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

View file

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

View file

@ -4,27 +4,11 @@ import 'package:postgres/postgres.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'models/car.dart'; import 'models/car.dart';
import 'models/car.orm.g.dart'; import 'models/car.orm.g.dart';
import 'common.dart';
final DateTime MILENNIUM = new DateTime.utc(2000, 1, 1); final DateTime MILENNIUM = new DateTime.utc(2000, 1, 1);
main() { 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', () { test('to where', () {
var query = new CarQuery(); var query = new CarQuery();
query.where query.where
@ -33,7 +17,7 @@ main() {
var whereClause = query.where.toWhereClause(); var whereClause = query.where.toWhereClause();
print('Where clause: $whereClause'); print('Where clause: $whereClause');
expect(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', () { test('parseRow', () {
@ -41,7 +25,7 @@ main() {
0, 0,
'Mazda', 'Mazda',
'CX9', 'CX9',
1, true,
DATE_YMD_HMS.format(MILENNIUM), DATE_YMD_HMS.format(MILENNIUM),
DATE_YMD_HMS.format(MILENNIUM), DATE_YMD_HMS.format(MILENNIUM),
DATE_YMD_HMS.format(MILENNIUM) DATE_YMD_HMS.format(MILENNIUM)
@ -61,5 +45,89 @@ main() {
startsWith(car.updatedAt.toIso8601String())); 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(); final AuthorQueryWhere where = new AuthorQueryWhere();
void and(AuthorQuery other) { void and(AuthorQuery other) {
var compiled = other.where.toWhereClause(); var compiled = other.where.toWhereClause(keyword: false);
if (compiled != null) { if (compiled != null) {
_and.add(compiled); _and.add(compiled);
} }
} }
void or(AuthorQuery other) { void or(AuthorQuery other) {
var compiled = other.where.toWhereClause(); var compiled = other.where.toWhereClause(keyword: false);
if (compiled != null) { if (compiled != null) {
_or.add(compiled); _or.add(compiled);
} }
} }
void not(AuthorQuery other) { void not(AuthorQuery other) {
var compiled = other.where.toWhereClause(); var compiled = other.where.toWhereClause(keyword: false);
if (compiled != null) { if (compiled != null) {
_not.add(compiled); _not.add(compiled);
} }
@ -46,6 +46,15 @@ class AuthorQuery {
if (whereClause != null) { if (whereClause != null) {
buf.write(' ' + whereClause); 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(';'); buf.write(';');
return buf.toString(); return buf.toString();
} }
@ -54,8 +63,8 @@ class AuthorQuery {
return new Author.fromJson({ return new Author.fromJson({
'id': row[0].toString(), 'id': row[0].toString(),
'name': row[1], 'name': row[1],
'created_at': DATE_YMD_HMS.parse(row[2]), 'created_at': row[2],
'updated_at': DATE_YMD_HMS.parse(row[3]) 'updated_at': row[3]
}); });
} }
@ -73,24 +82,39 @@ class AuthorQuery {
substitutionValues: {'id': id}).then((rows) => parseRow(rows.first)); substitutionValues: {'id': id}).then((rows) => parseRow(rows.first));
} }
Future<Author> update(int id, PostgreSQLConnection connection, Stream<Author> update(PostgreSQLConnection connection,
{String name, DateTime createdAt, DateTime updatedAt}) async { {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 __ormNow__ = new DateTime.now();
var result = await connection.query( var ctrl = new StreamController<Author>();
'UPDATE "authors" SET ("name", "created_at", "updated_at") = (@name, @createdAt, @updatedAt) WHERE "id" = @id RETURNING ("id", "name", "created_at", "updated_at");', connection.query(
buf.toString() +
' RETURNING ("id", "name", "created_at", "updated_at");',
substitutionValues: { substitutionValues: {
'name': name, 'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__, 'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__, 'updatedAt': updatedAt != null ? updatedAt : __ormNow__
'id': id }).then((rows) {
}); rows.map(parseRow).forEach(ctrl.add);
return parseRow(result); 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 __ormBeforeDelete__ = await AuthorQuery.getOne(id, connection);
var result = await connection.execute( var result = await connection.execute(
'DELETE FROM "authors" WHERE id = @id LIMIT 1;', 'DELETE FROM "authors" WHERE id = @id;',
substitutionValues: {'id': id}); substitutionValues: {'id': id});
if (result != 1) { if (result != 1) {
new StateError('DELETE query deleted ' + new StateError('DELETE query deleted ' +
@ -103,14 +127,19 @@ class AuthorQuery {
static Future<Author> insert(PostgreSQLConnection connection, static Future<Author> insert(PostgreSQLConnection connection,
{String name, DateTime createdAt, DateTime updatedAt}) async { {String name, DateTime createdAt, DateTime updatedAt}) async {
var __ormNow__ = new DateTime.now(); var __ormNow__ = new DateTime.now();
var result = await connection.query( var nRows = await connection.execute(
'INSERT INTO "authors" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt) RETURNING ("id", "name", "created_at", "updated_at");', 'INSERT INTO "authors" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt);',
substitutionValues: { substitutionValues: {
'name': name, 'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__, 'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __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) => static Stream<Author> getAll(PostgreSQLConnection connection) =>
@ -118,7 +147,8 @@ class AuthorQuery {
} }
class AuthorQueryWhere { class AuthorQueryWhere {
final StringSqlExpressionBuilder id = new StringSqlExpressionBuilder(); final NumericSqlExpressionBuilder<int> id =
new NumericSqlExpressionBuilder<int>();
final StringSqlExpressionBuilder name = new StringSqlExpressionBuilder(); final StringSqlExpressionBuilder name = new StringSqlExpressionBuilder();
@ -128,7 +158,7 @@ class AuthorQueryWhere {
final DateTimeSqlExpressionBuilder updatedAt = final DateTimeSqlExpressionBuilder updatedAt =
new DateTimeSqlExpressionBuilder('updated_at'); new DateTimeSqlExpressionBuilder('updated_at');
String toWhereClause() { String toWhereClause({bool keyword}) {
final List<String> expressions = []; final List<String> expressions = [];
if (id.hasValue) { if (id.hasValue) {
expressions.add('"id" ' + id.compile()); expressions.add('"id" ' + id.compile());
@ -142,6 +172,8 @@ class AuthorQueryWhere {
if (updatedAt.hasValue) { if (updatedAt.hasValue) {
expressions.add(updatedAt.compile()); 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, "id" serial,
"name" varchar, "name" varchar,
"created_at" timestamp, "created_at" timestamp,

View file

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

View file

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

View file

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