Generation complete... Needs tests + relations
This commit is contained in:
parent
f4dbca98fc
commit
2ecb60f16e
17 changed files with 723 additions and 147 deletions
|
@ -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" />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
12
test/models/author.dart
Normal 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;
|
||||
}
|
1
test/models/author.down.g.sql
Normal file
1
test/models/author.down.g.sql
Normal file
|
@ -0,0 +1 @@
|
|||
DROP TABLE "authors";
|
49
test/models/author.g.dart
Normal file
49
test/models/author.g.dart
Normal 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);
|
||||
}
|
147
test/models/author.orm.g.dart
Normal file
147
test/models/author.orm.g.dart
Normal 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 '));
|
||||
}
|
||||
}
|
6
test/models/author.up.g.sql
Normal file
6
test/models/author.up.g.sql
Normal 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
15
test/models/book.dart
Normal 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;
|
||||
}
|
1
test/models/book.down.g.sql
Normal file
1
test/models/book.down.g.sql
Normal file
|
@ -0,0 +1 @@
|
|||
DROP TABLE "books";
|
54
test/models/book.g.dart
Normal file
54
test/models/book.g.dart
Normal 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
149
test/models/book.orm.g.dart
Normal 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 '));
|
||||
}
|
||||
}
|
6
test/models/book.up.g.sql
Normal file
6
test/models/book.up.g.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
CREATE TABLE "books" (
|
||||
"id" serial,
|
||||
"name" varchar,
|
||||
"created_at" timestamp,
|
||||
"updated_at" timestamp
|
||||
);
|
|
@ -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");',
|
||||
|
|
Loading…
Reference in a new issue