Generation complete... Needs tests + relations

This commit is contained in:
thosakwe 2017-07-06 16:14:26 -04:00
parent f4dbca98fc
commit 2ecb60f16e
17 changed files with 723 additions and 147 deletions

View file

@ -12,6 +12,14 @@
<excludeFolder url="file://$MODULE_DIR$/tmp" />
<excludeFolder url="file://$MODULE_DIR$/tool/packages" />
</content>
<content url="file://$MODULE_DIR$/../serialize">
<excludeFolder url="file://$MODULE_DIR$/../serialize/.pub" />
<excludeFolder url="file://$MODULE_DIR$/../serialize/build" />
<excludeFolder url="file://$MODULE_DIR$/../serialize/packages" />
<excludeFolder url="file://$MODULE_DIR$/../serialize/test/models/packages" />
<excludeFolder url="file://$MODULE_DIR$/../serialize/test/packages" />
<excludeFolder url="file://$MODULE_DIR$/../serialize/tool/packages" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />

View file

@ -9,6 +9,7 @@ import '../../migration.dart';
import '../../relations.dart';
import 'package:angel_serialize/src/find_annotation.dart';
import 'package:source_gen/src/annotation.dart';
import 'package:source_gen/source_gen.dart';
import 'postgres_build_context.dart';
// TODO: Should add id, createdAt, updatedAt...
@ -25,27 +26,47 @@ PostgresBuildContext buildContext(
tableName: annotation.tableName?.isNotEmpty == true
? annotation.tableName
: pluralize(new ReCase(clazz.name).snakeCase));
var relations = new TypeChecker.fromRuntime(Relationship);
List<String> fieldNames = [];
List<FieldElement> fields = [];
for (var field in raw.fields) {
fieldNames.add(field.name);
// Check for relationship. If so, skip.
Relationship relationship = null;
var relationshipAnnotation = relations.firstAnnotationOf(field);
/* findAnnotation<HasOne>(field, HasOne) ??
findAnnotation<HasMany>(field, HasMany) ??
findAnnotation<BelongsTo>(field, BelongsTo);*/
bool isRelationship = field.metadata.any((ann) {
return matchAnnotation(Relationship, ann) ||
matchAnnotation(HasMany, ann) ||
matchAnnotation(HasOne, ann) ||
matchAnnotation(BelongsTo, ann);
});
if (relationship != null) {
ctx.relationships[field.name] = relationship;
continue;
} else if (isRelationship) {
ctx.relationships[field.name] = null;
if (relationshipAnnotation != null) {
int type = -1;
switch (relationshipAnnotation.type.name) {
case 'HasMany':
type = RelationshipType.HAS_MANY;
break;
case 'HasOne':
type = RelationshipType.HAS_ONE;
break;
case 'BelongsTo':
type = RelationshipType.BELONGS_TO;
break;
default:
throw new UnsupportedError(
'Unsupported relationship type "${relationshipAnnotation.type.name}".');
}
ctx.relationshipFields.add(field);
ctx.relationships[field.name] = new Relationship(type,
localKey:
relationshipAnnotation.getField('localKey')?.toStringValue(),
foreignKey:
relationshipAnnotation.getField('foreignKey')?.toStringValue(),
foreignTable:
relationshipAnnotation.getField('foreignTable')?.toStringValue(),
cascadeOnDelete: relationshipAnnotation
.getField('cascadeOnDelete')
?.toBoolValue());
continue;
}
@ -86,7 +107,9 @@ PostgresBuildContext buildContext(
if (column == null)
throw 'Cannot infer SQL column type for field "${field.name}" with type "${field.type.name}".';
ctx.columnInfo[field.name] = column;
fields.add(field);
}
ctx.fields.addAll(fields);
return ctx;
}

View file

@ -45,26 +45,52 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
lib.addDirective(new ImportBuilder('package:angel_orm/angel_orm.dart'));
lib.addDirective(new ImportBuilder('package:postgres/postgres.dart'));
lib.addDirective(new ImportBuilder(p.basename(buildStep.inputId.path)));
var elements = getElementsFromLibraryElement(libraryElement)
.where((el) => el is ClassElement);
Map<ClassElement, PostgresBuildContext> contexts = {};
List<String> done = [];
for (var element in getElementsFromLibraryElement(libraryElement)) {
if (element is ClassElement && !done.contains(element.name)) {
List<String> imported = [];
for (var element in elements) {
if (!done.contains(element.name)) {
var ann = element.metadata
.firstWhere((a) => matchAnnotation(ORM, a), orElse: () => null);
if (ann != null) {
var ctx = buildContext(
var ctx = contexts[element] = buildContext(
element,
instantiateAnnotation(ann),
buildStep,
resolver,
autoSnakeCaseNames != false,
autoIdAndDateFields != false);
lib.addMember(buildQueryClass(ctx));
lib.addMember(buildWhereClass(ctx));
done.add(element.name);
ctx.relationships.forEach((name, relationship) {
var field = ctx.resolveRelationshipField(name);
var uri = field.type.element.source.uri;
var pathName = p
.basenameWithoutExtension(p.basenameWithoutExtension(uri.path));
var source =
'$pathName.orm.g.dart'; //uri.resolve('$pathName.orm.g.dart').toString();
// TODO: Find good way to source url...
source = new ReCase(field.type.name).snakeCase + '.orm.g.dart';
if (!imported.contains(source)) {
lib.addDirective(new ImportBuilder(source));
imported.add(source);
}
});
}
}
}
done.clear();
for (var element in contexts.keys) {
if (!done.contains(element.name)) {
var ctx = contexts[element];
lib.addMember(buildQueryClass(ctx));
lib.addMember(buildWhereClass(ctx));
done.add(element.name);
}
}
return lib;
}
@ -111,7 +137,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
clazz.addMethod(buildGetMethod(ctx));
// Add getOne()...
clazz.addMethod(buildGetOneMethod(ctx));
clazz.addMethod(buildGetOneMethod(ctx), asStatic: true);
// Add update()...
clazz.addMethod(buildUpdateMethod(ctx));
@ -170,12 +196,11 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
int i = 0;
// TODO: Support relations...
ctx.fields.forEach((field) {
var name = ctx.resolveFieldName(field.name);
var rowKey = row[literal(i++)];
if (false && field.type.name == 'DateTime') {
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')) {
@ -186,6 +211,17 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
data[name] = rowKey;
});
ctx.relationships.forEach((name, relationship) {
var field = ctx.resolveRelationshipField(name);
var alias = ctx.resolveFieldName(name);
var idx = i++;
var rowKey = row[literal(idx)];
data[alias] = (row.property('length') < literal(idx + 1)).ternary(
literal(null),
new TypeBuilder(new ReCase(field.type.name).pascalCase + 'Query')
.invoke('parseRow', [rowKey]));
});
// Then, call a .fromJson() constructor
meth.addStatement(new TypeBuilder(ctx.modelClassName)
.newInstance([map(data)], constructor: 'fromJson').asReturn());
@ -239,22 +275,151 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
return meth;
}
void _addAllNamed(MethodBuilder meth, PostgresBuildContext ctx) {
// Add all named params
ctx.fields.forEach((field) {
if (field.name != 'id') {
var p = new ParameterBuilder(field.name,
type: new TypeBuilder(field.type.name));
var column = ctx.columnInfo[field.name];
if (column?.defaultValue != null)
p = p.asOptional(literal(column.defaultValue));
meth.addNamed(p);
}
});
}
void _addReturning(StringBuffer buf, PostgresBuildContext ctx) {
buf.write(' RETURNING (');
int i = 0;
ctx.fields.forEach((field) {
if (i++ > 0) buf.write(', ');
var name = ctx.resolveFieldName(field.name);
buf.write('"$name"');
});
buf.write(');');
}
void _ensureDates(MethodBuilder meth, PostgresBuildContext ctx) {
if (ctx.fields.any((f) => f.name == 'createdAt' || f.name == 'updatedAt')) {
meth.addStatement(varField('__ormNow__',
value: lib$core.DateTime.newInstance([], constructor: 'now')));
}
}
Map<String, ExpressionBuilder> _buildSubstitutionValues(
PostgresBuildContext ctx) {
Map<String, ExpressionBuilder> substitutionValues = {};
ctx.fields.forEach((field) {
if (field.name == 'id')
return;
else if (field.name == 'createdAt' || field.name == 'updatedAt') {
var ref = reference(field.name);
substitutionValues[field.name] =
ref.notEquals(literal(null)).ternary(ref, reference('__ormNow__'));
} else
substitutionValues[field.name] = reference(field.name);
});
return substitutionValues;
}
void _executeQuery(StringBuffer buf, 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());
}
MethodBuilder buildUpdateMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('update',
modifier: MethodModifier.asAsync,
returnType: new TypeBuilder('Future',
genericTypes: [new TypeBuilder(ctx.modelClassName)]));
meth.addPositional(parameter('id', [lib$core.int]));
meth.addPositional(
parameter('connection', [new TypeBuilder('PostgreSQLConnection')]));
_addAllNamed(meth, ctx);
var buf = new StringBuffer('UPDATE "${ctx.tableName}" SET (');
int i = 0;
ctx.fields.forEach((field) {
if (field.name == 'id')
return;
else {
if (i++ > 0) buf.write(', ');
var key = ctx.resolveFieldName(field.name);
buf.write('"$key"');
}
});
buf.write(') = (');
i = 0;
ctx.fields.forEach((field) {
if (field.name == 'id')
return;
else {
if (i++ > 0) buf.write(', ');
buf.write('@${field.name}');
}
});
buf.write(') WHERE "id" = @id');
_addReturning(buf, ctx);
_ensureDates(meth, ctx);
var substitutionValues = _buildSubstitutionValues(ctx);
substitutionValues.putIfAbsent('id', () => reference('id'));
_executeQuery(buf, meth, substitutionValues);
return meth;
}
MethodBuilder buildDeleteMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('delete',
modifier: MethodModifier.asAsync,
returnType: new TypeBuilder('Future',
genericTypes: [new TypeBuilder(ctx.modelClassName)]));
genericTypes: [new TypeBuilder(ctx.modelClassName)]))
..addPositional(parameter('id', [lib$core.int]))
..addPositional(
parameter('connection', [new TypeBuilder('PostgreSQLConnection')]));
var id = reference('id');
var connection = reference('connection');
var beforeDelete = reference('__ormBeforeDelete__');
var result = reference('result');
// var __ormBeforeDelete__ = await XQuery.getOne(id, connection);
meth.addStatement(varField('__ormBeforeDelete__',
value: new TypeBuilder(ctx.queryClassName)
.invoke('getOne', [id, connection]).asAwait()));
// await connection.execute('...');
meth.addStatement(varField('result',
value: connection.invoke('execute', [
literal('DELETE FROM "${ctx.tableName}" WHERE id = @id LIMIT 1;')
], namedArguments: {
'substitutionValues': map({'id': id})
}).asAwait()));
meth.addStatement(ifThen(result.notEquals(literal(1)), [
lib$core.StateError.newInstance([
literal('DELETE query deleted ') +
result +
literal(' row(s), instead of exactly 1 row.')
])
]));
// return __ormBeforeDelete__
meth.addStatement(beforeDelete.asReturn());
return meth;
}
MethodBuilder buildInsertMethod(PostgresBuildContext ctx) {
// TODO: Auto-set createdAt, updatedAt...
var meth = new MethodBuilder('insert',
modifier: MethodModifier.asAsync,
returnType: new TypeBuilder('Future',
@ -263,14 +428,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
parameter('connection', [new TypeBuilder('PostgreSQLConnection')]));
// Add all named params
ctx.fields.forEach((field) {
var p = new ParameterBuilder(field.name,
type: new TypeBuilder(field.type.name));
var column = ctx.columnInfo[field.name];
if (column?.defaultValue != null)
p = p.asOptional(literal(column.defaultValue));
meth.addNamed(p);
});
_addAllNamed(meth, ctx);
var buf = new StringBuffer('INSERT INTO "${ctx.tableName}" (');
int i = 0;
@ -297,104 +455,13 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
buf.write(')');
buf.write(' RETURNING (');
i = 0;
ctx.fields.forEach((field) {
if (i++ > 0) buf.write(', ');
var name = ctx.resolveFieldName(field.name);
buf.write('"$name"');
});
_addReturning(buf, ctx);
// meth.addStatement(lib$core.print.call([literal(buf.toString())]));
buf.write(');');
meth.addStatement(lib$core.print.call([literal(buf.toString())]));
_ensureDates(meth, ctx);
if (ctx.fields.any((f) => f.name == 'createdAt' || f.name == 'updatedAt')) {
meth.addStatement(varField('__ormNow__',
value: lib$core.DateTime.newInstance([], constructor: 'now')));
}
Map<String, ExpressionBuilder> substitutionValues = {};
ctx.fields.forEach((field) {
if (field.name == 'id')
return;
else if (field.name == 'createdAt' || field.name == 'updatedAt') {
var ref = reference(field.name);
substitutionValues[field.name] =
ref.notEquals(literal(null)).ternary(ref, reference('__ormNow__'));
} else
substitutionValues[field.name] = reference(field.name);
});
/*
// Create StringBuffer
meth.addStatement(varField('buf',
type: lib$core.StringBuffer,
value: lib$core.StringBuffer.newInstance([])));
var buf = reference('buf');
// Create "INSERT INTO segment"
var fieldNames = ctx.fields
.map((f) => ctx.resolveFieldName(f.name))
.map((k) => '"$k"')
.join(', ');
var insertInto =
literal('INSERT INTO "${ctx.tableName}" ($fieldNames) VALUES (');
meth.addStatement(buf.invoke('write', [insertInto]));
// Write all fields
int i = 0;
var backtick = literal('"');
var numType = ctx.typeProvider.numType;
var boolType = ctx.typeProvider.boolType;
ctx.fields.forEach((field) {
var ref = reference(field.name);
ExpressionBuilder value;
// Handle numbers
if (field.type.isAssignableTo(numType)) {
value = ref;
}
// Handle boolean
else if (field.type.isAssignableTo(numType)) {
value = ref.equals(literal(true)).ternary(literal(1), literal(0));
}
// Handle DateTime
else if (field.type.isAssignableTo(ctx.dateTimeType)) {
// TODO: DATE and not just DATETIME
value = reference('DATE_YMD_HMS').invoke('format', [ref]);
}
// Handle anything else...
// TODO: Escape SQL strings???
else {
value = backtick + (ref.invoke('toString', [])) + backtick;
}
if (i++ > 0) meth.addStatement(buf.invoke('write', [literal(', ')]));
meth.addStatement(ifThen(ref.equals(literal(null)), [
buf.invoke('write', [literal('NULL')]),
elseThen([
buf.invoke('write', [value])
])
]));
});
// Finalize buffer
meth.addStatement(buf.invoke('write', [literal(');')]));
meth.addStatement(varField('query', value: buf.invoke('toString', [])));*/
var connection = reference('connection');
var query = literal(buf.toString());
var result = reference('result');
meth.addStatement(varField('result',
value: connection.invoke('query', [
query
], namedArguments: {
'substitutionValues': map(substitutionValues)
}).asAwait()));
meth.addStatement(reference('parseRow').call([result]).asReturn());
var substitutionValues = _buildSubstitutionValues(ctx);
_executeQuery(buf, meth, substitutionValues);
return meth;
}

View file

@ -28,7 +28,7 @@ class PostgresBuildContext extends BuildContext {
originalClassName: raw.originalClassName,
sourceFilename: raw.sourceFilename);
List<FieldElement> get fields => raw.fields;
final List<FieldElement> fields = [], relationshipFields = [];
Map<String, String> get aliases => raw.aliases;
@ -53,4 +53,7 @@ class PostgresBuildContext extends BuildContext {
TypeProvider get typeProvider =>
_typeProviderCache ??= library.context.typeProvider;
FieldElement resolveRelationshipField(String name) =>
relationshipFields.firstWhere((f) => f.name == name, orElse: () => null);
}

View file

@ -1,10 +1,17 @@
abstract class RelationshipType {
static const int HAS_MANY = 0;
static const int HAS_ONE = 1;
static const int BELONGS_TO = 2;
}
class Relationship {
final int type;
final String localKey;
final String foreignKey;
final String foreignTable;
final bool cascadeOnDelete;
const Relationship._(
const Relationship(this.type,
{this.localKey,
this.foreignKey,
this.foreignTable,
@ -17,7 +24,7 @@ class HasMany extends Relationship {
String foreignKey,
String foreignTable,
bool cascadeOnDelete: false})
: super._(
: super(0,
localKey: localKey,
foreignKey: foreignKey,
foreignTable: foreignTable,
@ -32,7 +39,7 @@ class HasOne extends Relationship {
String foreignKey,
String foreignTable,
bool cascadeOnDelete: false})
: super._(
: super(1,
localKey: localKey,
foreignKey: foreignKey,
foreignTable: foreignTable,
@ -44,7 +51,7 @@ const HasOne hasOne = const HasOne();
class BelongsTo extends Relationship {
const BelongsTo(
{String localKey: 'id', String foreignKey, String foreignTable})
: super._(
: super(2,
localKey: localKey,
foreignKey: foreignKey,
foreignTable: foreignTable);

View file

@ -13,7 +13,7 @@ dependencies:
postgres: ">=0.9.5 <1.0.0"
query_builder_sql: ^1.0.0-alpha
recase: ^1.0.0
source_gen: ^0.5.8
source_gen: ^0.6.0
dev_dependencies:
angel_diagnostics: ">=1.0.0 <2.0.0"
angel_serialize:

12
test/models/author.dart Normal file
View file

@ -0,0 +1,12 @@
library angel_orm.test.models.author;
import 'package:angel_framework/common.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize/angel_serialize.dart';
part 'author.g.dart';
@serializable
@orm
class _Author extends Model {
String name;
}

View file

@ -0,0 +1 @@
DROP TABLE "authors";

49
test/models/author.g.dart Normal file
View file

@ -0,0 +1,49 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of angel_orm.test.models.author;
// **************************************************************************
// Generator: JsonModelGenerator
// Target: class _Author
// **************************************************************************
class Author extends _Author {
@override
String id;
@override
String name;
@override
DateTime createdAt;
@override
DateTime updatedAt;
Author({this.id, this.name, this.createdAt, this.updatedAt});
factory Author.fromJson(Map data) {
return new Author(
id: data['id'],
name: data['name'],
createdAt: data['created_at'] is DateTime
? data['created_at']
: (data['created_at'] is String
? DateTime.parse(data['created_at'])
: null),
updatedAt: data['updated_at'] is DateTime
? data['updated_at']
: (data['updated_at'] is String
? DateTime.parse(data['updated_at'])
: null));
}
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'created_at': createdAt == null ? null : createdAt.toIso8601String(),
'updated_at': updatedAt == null ? null : updatedAt.toIso8601String()
};
static Author parse(Map map) => new Author.fromJson(map);
}

View file

@ -0,0 +1,147 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// Generator: PostgresORMGenerator
// Target: class _Author
// **************************************************************************
import 'dart:async';
import 'package:angel_orm/angel_orm.dart';
import 'package:postgres/postgres.dart';
import 'author.dart';
class AuthorQuery {
final List<String> _and = [];
final List<String> _or = [];
final List<String> _not = [];
final AuthorQueryWhere where = new AuthorQueryWhere();
void and(AuthorQuery other) {
var compiled = other.where.toWhereClause();
if (compiled != null) {
_and.add(compiled);
}
}
void or(AuthorQuery other) {
var compiled = other.where.toWhereClause();
if (compiled != null) {
_or.add(compiled);
}
}
void not(AuthorQuery other) {
var compiled = other.where.toWhereClause();
if (compiled != null) {
_not.add(compiled);
}
}
String toSql() {
var buf = new StringBuffer('SELECT * FROM "authors"');
var whereClause = where.toWhereClause();
if (whereClause != null) {
buf.write(' ' + whereClause);
}
buf.write(';');
return buf.toString();
}
static Author parseRow(List row) {
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])
});
}
Stream<Author> get(PostgreSQLConnection connection) {
StreamController<Author> ctrl = new StreamController<Author>();
connection.query(toSql()).then((rows) {
rows.map(parseRow).forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
static Future<Author> getOne(int id, PostgreSQLConnection connection) {
return connection.query('SELECT * FROM "authors" WHERE "id" = @id;',
substitutionValues: {'id': id}).then((rows) => parseRow(rows.first));
}
Future<Author> update(int id, PostgreSQLConnection connection,
{String name, DateTime createdAt, DateTime updatedAt}) async {
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");',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__,
'id': id
});
return parseRow(result);
}
Future<Author> delete(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;',
substitutionValues: {'id': id});
if (result != 1) {
new StateError('DELETE query deleted ' +
result +
' row(s), instead of exactly 1 row.');
}
return __ormBeforeDelete__;
}
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");',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
});
return parseRow(result);
}
static Stream<Author> getAll(PostgreSQLConnection connection) =>
new AuthorQuery().get(connection);
}
class AuthorQueryWhere {
final StringSqlExpressionBuilder id = new StringSqlExpressionBuilder();
final StringSqlExpressionBuilder name = new StringSqlExpressionBuilder();
final DateTimeSqlExpressionBuilder createdAt =
new DateTimeSqlExpressionBuilder('created_at');
final DateTimeSqlExpressionBuilder updatedAt =
new DateTimeSqlExpressionBuilder('updated_at');
String toWhereClause() {
final List<String> expressions = [];
if (id.hasValue) {
expressions.add('"id" ' + id.compile());
}
if (name.hasValue) {
expressions.add('"name" ' + name.compile());
}
if (createdAt.hasValue) {
expressions.add(createdAt.compile());
}
if (updatedAt.hasValue) {
expressions.add(updatedAt.compile());
}
return expressions.isEmpty ? null : ('WHERE ' + expressions.join(' AND '));
}
}

View file

@ -0,0 +1,6 @@
CREATE TABLE "authors" (
"id" serial,
"name" varchar,
"created_at" timestamp,
"updated_at" timestamp
);

15
test/models/book.dart Normal file
View file

@ -0,0 +1,15 @@
library angel_orm.test.models.author;
import 'package:angel_framework/common.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize/angel_serialize.dart';
import 'author.dart';
part 'book.g.dart';
@serializable
@orm
class _Book extends Model {
@belongsTo
Author author;
String name;
}

View file

@ -0,0 +1 @@
DROP TABLE "books";

54
test/models/book.g.dart Normal file
View file

@ -0,0 +1,54 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of angel_orm.test.models.author;
// **************************************************************************
// Generator: JsonModelGenerator
// Target: class _Book
// **************************************************************************
class Book extends _Book {
@override
String id;
@override
dynamic author;
@override
String name;
@override
DateTime createdAt;
@override
DateTime updatedAt;
Book({this.id, this.author, this.name, this.createdAt, this.updatedAt});
factory Book.fromJson(Map data) {
return new Book(
id: data['id'],
author: data['author'],
name: data['name'],
createdAt: data['created_at'] is DateTime
? data['created_at']
: (data['created_at'] is String
? DateTime.parse(data['created_at'])
: null),
updatedAt: data['updated_at'] is DateTime
? data['updated_at']
: (data['updated_at'] is String
? DateTime.parse(data['updated_at'])
: null));
}
Map<String, dynamic> toJson() => {
'id': id,
'author': author,
'name': name,
'created_at': createdAt == null ? null : createdAt.toIso8601String(),
'updated_at': updatedAt == null ? null : updatedAt.toIso8601String()
};
static Book parse(Map map) => new Book.fromJson(map);
}

149
test/models/book.orm.g.dart Normal file
View file

@ -0,0 +1,149 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// Generator: PostgresORMGenerator
// Target: class _Book
// **************************************************************************
import 'dart:async';
import 'package:angel_orm/angel_orm.dart';
import 'package:postgres/postgres.dart';
import 'book.dart';
import 'author.orm.g.dart';
class BookQuery {
final List<String> _and = [];
final List<String> _or = [];
final List<String> _not = [];
final BookQueryWhere where = new BookQueryWhere();
void and(BookQuery other) {
var compiled = other.where.toWhereClause();
if (compiled != null) {
_and.add(compiled);
}
}
void or(BookQuery other) {
var compiled = other.where.toWhereClause();
if (compiled != null) {
_or.add(compiled);
}
}
void not(BookQuery other) {
var compiled = other.where.toWhereClause();
if (compiled != null) {
_not.add(compiled);
}
}
String toSql() {
var buf = new StringBuffer('SELECT * FROM "books"');
var whereClause = where.toWhereClause();
if (whereClause != null) {
buf.write(' ' + whereClause);
}
buf.write(';');
return buf.toString();
}
static Book parseRow(List row) {
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]),
'author': row.length < 5 ? null : AuthorQuery.parseRow(row[4])
});
}
Stream<Book> get(PostgreSQLConnection connection) {
StreamController<Book> ctrl = new StreamController<Book>();
connection.query(toSql()).then((rows) {
rows.map(parseRow).forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
static Future<Book> getOne(int id, PostgreSQLConnection connection) {
return connection.query('SELECT * FROM "books" WHERE "id" = @id;',
substitutionValues: {'id': id}).then((rows) => parseRow(rows.first));
}
Future<Book> update(int id, PostgreSQLConnection connection,
{String name, DateTime createdAt, DateTime updatedAt}) async {
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");',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__,
'id': id
});
return parseRow(result);
}
Future<Book> delete(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;',
substitutionValues: {'id': id});
if (result != 1) {
new StateError('DELETE query deleted ' +
result +
' row(s), instead of exactly 1 row.');
}
return __ormBeforeDelete__;
}
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");',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
});
return parseRow(result);
}
static Stream<Book> getAll(PostgreSQLConnection connection) =>
new BookQuery().get(connection);
}
class BookQueryWhere {
final StringSqlExpressionBuilder id = new StringSqlExpressionBuilder();
final StringSqlExpressionBuilder name = new StringSqlExpressionBuilder();
final DateTimeSqlExpressionBuilder createdAt =
new DateTimeSqlExpressionBuilder('created_at');
final DateTimeSqlExpressionBuilder updatedAt =
new DateTimeSqlExpressionBuilder('updated_at');
String toWhereClause() {
final List<String> expressions = [];
if (id.hasValue) {
expressions.add('"id" ' + id.compile());
}
if (name.hasValue) {
expressions.add('"name" ' + name.compile());
}
if (createdAt.hasValue) {
expressions.add(createdAt.compile());
}
if (updatedAt.hasValue) {
expressions.add(updatedAt.compile());
}
return expressions.isEmpty ? null : ('WHERE ' + expressions.join(' AND '));
}
}

View file

@ -0,0 +1,6 @@
CREATE TABLE "books" (
"id" serial,
"name" varchar,
"created_at" timestamp,
"updated_at" timestamp
);

View file

@ -56,9 +56,9 @@ class CarQuery {
'make': row[1],
'description': row[2],
'family_friendly': row[3] == 1,
'recalled_at': row[4],
'created_at': row[5],
'updated_at': row[6]
'recalled_at': DATE_YMD_HMS.parse(row[4]),
'created_at': DATE_YMD_HMS.parse(row[5]),
'updated_at': DATE_YMD_HMS.parse(row[6])
});
}
@ -71,25 +71,53 @@ class CarQuery {
return ctrl.stream;
}
Future<Car> getOne(int id, PostgreSQLConnection connection) {
static Future<Car> getOne(int id, PostgreSQLConnection connection) {
return connection.query('SELECT * FROM "cars" WHERE "id" = @id;',
substitutionValues: {'id': id}).then((rows) => parseRow(rows.first));
}
Future<Car> update() {}
Future<Car> delete() {}
static Future<Car> insert(PostgreSQLConnection connection,
{String id,
String make,
Future<Car> update(int id, PostgreSQLConnection connection,
{String make,
String description,
bool familyFriendly,
DateTime recalledAt,
DateTime createdAt,
DateTime updatedAt}) async {
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");',
substitutionValues: {
'make': make,
'description': description,
'familyFriendly': familyFriendly,
'recalledAt': recalledAt,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__,
'id': id
});
return parseRow(result);
}
Future<Car> delete(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;',
substitutionValues: {'id': id});
if (result != 1) {
new StateError('DELETE query deleted ' +
result +
' row(s), instead of exactly 1 row.');
}
return __ormBeforeDelete__;
}
static Future<Car> insert(PostgreSQLConnection connection,
{String make,
String description,
bool familyFriendly,
DateTime recalledAt,
DateTime createdAt,
DateTime updatedAt}) async {
print(
'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 __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");',