All static methods working...
This commit is contained in:
parent
2ecb60f16e
commit
2d6ec2ed17
16 changed files with 468 additions and 189 deletions
15
README.md
15
README.md
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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 ');
|
||||
}
|
||||
|
|
|
@ -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
15
test/common.dart
Normal 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;
|
||||
}
|
|
@ -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 '));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
CREATE TABLE "authors" (
|
||||
CREATE TEMPORARY TABLE "authors" (
|
||||
"id" serial,
|
||||
"name" varchar,
|
||||
"created_at" timestamp,
|
||||
|
|
|
@ -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 '));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
CREATE TABLE "books" (
|
||||
CREATE TEMPORARY TABLE "books" (
|
||||
"id" serial,
|
||||
"name" varchar,
|
||||
"created_at" timestamp,
|
||||
|
|
|
@ -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 '));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in a new issue