Restore old annotations

This commit is contained in:
Tobe O 2018-08-24 08:30:38 -04:00
parent 5dca761978
commit 1e234ea177
20 changed files with 506 additions and 1289 deletions

View file

@ -2,10 +2,12 @@
<module type="WEB_MODULE" version="4"> <module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$/angel_orm"> <content url="file://$MODULE_DIR$/angel_orm">
<excludeFolder url="file://$MODULE_DIR$/angel_orm/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/angel_orm/.pub" /> <excludeFolder url="file://$MODULE_DIR$/angel_orm/.pub" />
<excludeFolder url="file://$MODULE_DIR$/angel_orm/build" /> <excludeFolder url="file://$MODULE_DIR$/angel_orm/build" />
</content> </content>
<content url="file://$MODULE_DIR$/angel_orm_generator"> <content url="file://$MODULE_DIR$/angel_orm_generator">
<excludeFolder url="file://$MODULE_DIR$/angel_orm_generator/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/angel_orm_generator/.pub" /> <excludeFolder url="file://$MODULE_DIR$/angel_orm_generator/.pub" />
<excludeFolder url="file://$MODULE_DIR$/angel_orm_generator/build" /> <excludeFolder url="file://$MODULE_DIR$/angel_orm_generator/build" />
</content> </content>

View file

@ -1,2 +0,0 @@
analyzer:
strong-mode: true

View file

@ -54,3 +54,5 @@ com_crashlytics_export_strings.xml
crashlytics.properties crashlytics.properties
crashlytics-build.properties crashlytics-build.properties
fabric.properties fabric.properties
.dart_tool

View file

@ -1,3 +1,8 @@
# 2.0.0-dev
* Restored all old PostgreSQL-specific annotations. Rather than a smart runtime,
having a codegen capable of building ORM's for multiple databases can potentially
provide a very fast ORM for everyone.
# 1.0.0-alpha+11 # 1.0.0-alpha+11
* Removed PostgreSQL-specific functionality, so that the ORM can ultimately * Removed PostgreSQL-specific functionality, so that the ORM can ultimately
target all services. target all services.

View file

@ -0,0 +1,3 @@
analyzer:
strong-mode:
implicit-casts: false

View file

@ -1,27 +1,27 @@
import 'package:angel_model/angel_model.dart'; import 'package:angel_model/angel_model.dart';
import 'package:angel_orm/angel_orm.dart'; import 'package:angel_orm/angel_orm.dart';
Query findEmployees(Company company) { main() {
return new Query()
..['company_id'] = equals(company.id)
..['first_name'] = notNull() & (equals('John'))
..['salary'] = greaterThanOrEqual(100000.0);
}
@ORM('api/companies')
class Company extends Model {
String name;
bool isFortune500;
} }
@orm @orm
class Employee extends Model { abstract class Company extends Model {
String get name;
bool get isFortune500;
}
@orm
abstract class _Employee extends Model {
@belongsTo @belongsTo
Company company; Company get company;
String firstName, lastName; String get firstName;
double salary; String get lastName;
double get salary;
bool get isFortune500Employee => company.isFortune500; bool get isFortune500Employee => company.isFortune500;
} }

View file

@ -1,3 +1,4 @@
export 'src/annotations.dart'; export 'src/annotations.dart';
export 'src/query.dart'; export 'src/migration.dart';
export 'src/relations.dart'; export 'src/relations.dart';
export 'src/query.dart';

View file

@ -1,30 +1,17 @@
const ORM orm = const ORM(); const ORM orm = const ORM();
class ORM { class ORM {
/// The path to an Angel service that queries objects of the final String tableName;
/// annotated type at runtime.
/// const ORM([this.tableName]);
/// Ex. `api/users`, etc.
final String servicePath;
const ORM([this.servicePath]);
} }
/// Specifies that the ORM should build a join builder class CanJoin {
/// that combines the results of queries on two services.
class Join {
/// The [Model] type to join against.
final Type type; final Type type;
final String foreignKey;
/// The path to an Angel service that queries objects of the
/// [type] being joined against, at runtime.
///
/// Ex. `api/users`, etc.
final String servicePath;
/// The type of join this is.
final JoinType joinType; final JoinType joinType;
const Join(this.type, this.servicePath, [this.joinType = JoinType.join]); const CanJoin(this.type, this.foreignKey, {this.joinType: JoinType.full});
} }
/// The various types of [Join]. /// The various types of [Join].

View file

@ -0,0 +1,124 @@
const List<String> SQL_RESERVED_WORDS = const [
'SELECT',
'UPDATE',
'INSERT',
'DELETE',
'FROM',
'ASC',
'DESC',
'VALUES',
'RETURNING',
'ORDER',
'BY',
];
/// Applies additional attributes to a database column.
class Column {
/// If `true`, a SQL field will be nullable.
final bool isNullable;
/// Specifies the length of a `VARCHAR`.
final int length;
/// Explicitly defines a SQL type for this column.
final ColumnType type;
/// Specifies what kind of index this column is, if any.
final IndexType indexType;
/// The default value of this field.
final defaultValue;
const Column(
{this.isNullable: true,
this.length,
this.type,
this.indexType: IndexType.none,
this.defaultValue});
}
class PrimaryKey extends Column {
const PrimaryKey({ColumnType columnType})
: super(
type: columnType ?? ColumnType.serial, indexType: IndexType.primaryKey);
}
const Column primaryKey = const PrimaryKey();
/// Maps to SQL index types.
enum IndexType {
none,
/// Standard index.
standardIndex,
/// A primary key.
primaryKey,
/// A *unique* index.
unique
}
/// Maps to SQL data types.
///
/// Features all types from this list: http://www.tutorialspoint.com/sql/sql-data-types.htm
class ColumnType {
/// The name of this data type.
final String name;
const ColumnType(this.name);
static const ColumnType boolean = const ColumnType('boolean');
static const ColumnType smallSerial = const ColumnType('smallserial');
static const ColumnType serial = const ColumnType('serial');
static const ColumnType bigSerial = const ColumnType('bigserial');
// Numbers
static const ColumnType bigInt = const ColumnType('bigint');
static const ColumnType int = const ColumnType('int');
static const ColumnType smallInt = const ColumnType('smallint');
static const ColumnType tinyInt = const ColumnType('tinyint');
static const ColumnType bit = const ColumnType('bit');
static const ColumnType decimal = const ColumnType('decimal');
static const ColumnType numeric = const ColumnType('numeric');
static const ColumnType money = const ColumnType('money');
static const ColumnType smallMoney = const ColumnType('smallmoney');
static const ColumnType float = const ColumnType('float');
static const ColumnType real = const ColumnType('real');
// Dates and times
static const ColumnType dateTime = const ColumnType('datetime');
static const ColumnType smallDateTime = const ColumnType('smalldatetime');
static const ColumnType date = const ColumnType('date');
static const ColumnType time = const ColumnType('time');
static const ColumnType timeStamp = const ColumnType('timestamp');
static const ColumnType timeStampWithTimeZone =
const ColumnType('timestamp with time zone');
// Strings
static const ColumnType char = const ColumnType('char');
static const ColumnType varChar = const ColumnType('varchar');
static const ColumnType varCharMax = const ColumnType('varchar(max)');
static const ColumnType text = const ColumnType('text');
// Unicode strings
static const ColumnType nChar = const ColumnType('nchar');
static const ColumnType nVarChar = const ColumnType('nvarchar');
static const ColumnType nVarCharMax = const ColumnType('nvarchar(max)');
static const ColumnType nText = const ColumnType('ntext');
// Binary
static const ColumnType binary = const ColumnType('binary');
static const ColumnType varBinary = const ColumnType('varbinary');
static const ColumnType varBinaryMax = const ColumnType('varbinary(max)');
static const ColumnType image = const ColumnType('image');
// Misc.
static const ColumnType sqlVariant = const ColumnType('sql_variant');
static const ColumnType uniqueIdentifier =
const ColumnType('uniqueidentifier');
static const ColumnType xml = const ColumnType('xml');
static const ColumnType cursor = const ColumnType('cursor');
static const ColumnType table = const ColumnType('table');
}

View file

@ -1,114 +1,340 @@
/// Expects a field to be equal to a given [value]. import 'package:intl/intl.dart';
Predicate<T> equals<T>(T value) => import 'package:string_scanner/string_scanner.dart';
new Predicate<T>._(PredicateType.equals, value);
/// Expects at least one of the given [predicates] to be true. final DateFormat dateYmd = new DateFormat('yyyy-MM-dd');
Predicate<T> anyOf<T>(Iterable<Predicate<T>> predicates) => final DateFormat dateYmdHms = new DateFormat('yyyy-MM-dd HH:mm:ss');
new MultiPredicate<T>._(PredicateType.any, predicates);
/// Expects a field to be contained within a set of [values]. /// Cleans an input SQL expression of common SQL injection points.
Predicate<T> isIn<T>(Iterable<T> values) => new Predicate<T>._(PredicateType.isIn, null, values); String sanitizeExpression(String unsafe) {
var buf = new StringBuffer();
var scanner = new StringScanner(unsafe);
int ch;
/// Expects a field to be `null`. while (!scanner.isDone) {
Predicate<T> isNull<T>() => equals(null); // Ignore comment starts
if (scanner.scan('--') || scanner.scan('/*'))
continue;
/// Expects a given [predicate] to not be true. // Ignore all single quotes and attempted escape sequences
Predicate<T> not<T>(Predicate<T> predicate) => else if (scanner.scan("'") || scanner.scan('\\'))
new MultiPredicate<T>._(PredicateType.negate, [predicate]); continue;
/// Expects a field to be not be `null`. // Otherwise, add the next char, unless it's a null byte.
Predicate<T> notNull<T>() => not(isNull()); else if ((ch = scanner.readChar()) != 0 && ch != null)
buf.writeCharCode(ch);
/// Expects a field to be less than a given [value].
Predicate<T> lessThan<T>(T value) =>
new Predicate<T>._(PredicateType.less, value);
/// Expects a field to be less than or equal to a given [value].
Predicate<T> lessThanOrEqual<T>(T value) => lessThan(value) | equals(value);
/// Expects a field to be greater than a given [value].
Predicate<T> greaterThan<T>(T value) =>
new Predicate<T>._(PredicateType.greater, value);
/// Expects a field to be greater than or equal to a given [value].
Predicate<T> greaterThanOrEqual<T>(T value) =>
greaterThan(value) | equals(value);
/// A generic query class.
///
/// Angel services can translate these into driver-specific queries.
/// This allows the Angel ORM to be flexible and support multiple platforms.
class Query {
final Map<String, Predicate> _fields = {};
final Map<String, SortType> _sort = {};
/// Each field in a query is actually a [Predicate], and therefore acts as a contract
/// with the underlying service.
Map<String, Predicate> get fields =>
new Map<String, Predicate>.unmodifiable(_fields);
/// The sorting order applied to this query.
Map<String, SortType> get sorting =>
new Map<String, SortType>.unmodifiable(_sort);
/// Sets the [Predicate] assigned to the given [key].
void operator []=(String key, Predicate value) => _fields[key] = value;
/// Gets the [Predicate] assigned to the given [key].
Predicate operator [](String key) => _fields[key];
/// Sort output by the given [key].
void sortBy(String key, [SortType type = SortType.descending]) =>
_sort[key] = type;
}
/// A mechanism used to express an expectation about some object ([target]).
class Predicate<T> {
/// The type of expectation we are declaring.
final PredicateType type;
/// The single argument of this target.
final T target;
final Iterable<T> args;
Predicate._(this.type, this.target, [this.args]);
Predicate<T> operator &(Predicate<T> other) => and(other);
Predicate<T> operator |(Predicate<T> other) => or(other);
Predicate<T> and(Predicate<T> other) {
return new MultiPredicate._(PredicateType.and, [this, other]);
} }
Predicate<T> or(Predicate<T> other) { return buf.toString();
return new MultiPredicate._(PredicateType.or, [this, other]); }
abstract class SqlExpressionBuilder<T> {
bool get hasValue;
String compile();
void isBetween(T lower, T upper);
void isNotBetween(T lower, T upper);
void isIn(Iterable<T> values);
void isNotIn(Iterable<T> values);
}
class NumericSqlExpressionBuilder<T extends num>
implements SqlExpressionBuilder<T> {
bool _hasValue = false;
String _op = '=';
String _raw;
T _value;
@override
bool get hasValue => _hasValue;
bool _change(String op, T value) {
_raw = null;
_op = op;
_value = value;
return _hasValue = true;
}
@override
String compile() {
if (_raw != null) return _raw;
if (_value == null) return null;
return '$_op $_value';
}
operator <(T value) => _change('<', value);
operator >(T value) => _change('>', value);
operator <=(T value) => _change('<=', value);
operator >=(T value) => _change('>=', value);
void lessThan(T value) {
_change('<', value);
}
void lessThanOrEqualTo(T value) {
_change('<=', value);
}
void greaterThan(T value) {
_change('>', value);
}
void greaterThanOrEqualTo(T value) {
_change('>=', value);
}
void equals(T value) {
_change('=', value);
}
void notEquals(T value) {
_change('!=', value);
}
@override
void isBetween(T lower, T upper) {
_raw = 'BETWEEN $lower AND $upper';
_hasValue = true;
}
@override
void isNotBetween(T lower, T upper) {
_raw = 'NOT BETWEEN $lower AND $upper';
_hasValue = true;
}
@override
void isIn(Iterable<T> values) {
_raw = 'IN (' + values.join(', ') + ')';
_hasValue = true;
}
@override
void isNotIn(Iterable<T> values) {
_raw = 'NOT IN (' + values.join(', ') + ')';
_hasValue = true;
} }
} }
/// An advanced [Predicate] that performs an operation of multiple other predicates. class StringSqlExpressionBuilder implements SqlExpressionBuilder<String> {
class MultiPredicate<T> extends Predicate<T> { bool _hasValue = false;
final Iterable<Predicate<T>> targets; String _op = '=', _raw, _value;
MultiPredicate._(PredicateType type, this.targets) : super._(type, null); @override
bool get hasValue => _hasValue;
/// Use [targets] instead. bool _change(String op, String value) {
@deprecated _raw = null;
T get target => throw new UnsupportedError( _op = op;
'IterablePredicate has no `target`. Use `targets` instead.'); _value = value;
return _hasValue = true;
}
@override
String compile() {
if (_raw != null) return _raw;
if (_value == null) return null;
var v = sanitizeExpression(_value);
return "$_op '$v'";
}
void isEmpty() => equals('');
void equals(String value) {
_change('=', value);
}
void notEquals(String value) {
_change('!=', value);
}
void like(String value) {
_change('LIKE', value);
}
@override
void isBetween(String lower, String upper) {
var l = sanitizeExpression(lower), u = sanitizeExpression(upper);
_raw = "BETWEEN '$l' AND '$u'";
_hasValue = true;
}
@override
void isNotBetween(String lower, String upper) {
var l = sanitizeExpression(lower), u = sanitizeExpression(upper);
_raw = "NOT BETWEEN '$l' AND '$u'";
_hasValue = true;
}
@override
void isIn(Iterable<String> values) {
_raw = 'IN (' +
values.map(sanitizeExpression).map((s) => "'$s'").join(', ') +
')';
_hasValue = true;
}
@override
void isNotIn(Iterable<String> values) {
_raw = 'NOT IN (' +
values.map(sanitizeExpression).map((s) => "'$s'").join(', ') +
')';
_hasValue = true;
}
} }
/// The various types of predicate. class BooleanSqlExpressionBuilder implements SqlExpressionBuilder<bool> {
enum PredicateType { bool _hasValue = false;
equals, String _op = '=', _raw;
any, bool _value;
isIn,
negate, @override
and, bool get hasValue => _hasValue;
or,
less, bool _change(String op, bool value) {
greater, _raw = null;
_op = op;
_value = value;
return _hasValue = true;
}
@override
String compile() {
if (_raw != null) return _raw;
if (_value == null) return null;
var v = _value ? 'TRUE' : 'FALSE';
return '$_op $v';
}
void equals(bool value) {
_change('=', value);
}
void notEquals(bool value) {
_change('!=', value);
}
@override
void isBetween(bool lower, bool upper) => throw new UnsupportedError(
'Booleans do not support BETWEEN expressions.');
@override
void isNotBetween(bool lower, bool upper) => isBetween(lower, upper);
@override
void isIn(Iterable<bool> values) {
_raw = 'IN (' + values.map((b) => b ? 'TRUE' : 'FALSE').join(', ') + ')';
_hasValue = true;
}
@override
void isNotIn(Iterable<bool> values) {
_raw =
'NOT IN (' + values.map((b) => b ? 'TRUE' : 'FALSE').join(', ') + ')';
_hasValue = true;
}
} }
/// The various modes of sorting. class DateTimeSqlExpressionBuilder implements SqlExpressionBuilder<DateTime> {
enum SortType { ascending, descending } final NumericSqlExpressionBuilder<int> year =
new NumericSqlExpressionBuilder<int>(),
month = new NumericSqlExpressionBuilder<int>(),
day = new NumericSqlExpressionBuilder<int>(),
hour = new NumericSqlExpressionBuilder<int>(),
minute = new NumericSqlExpressionBuilder<int>(),
second = new NumericSqlExpressionBuilder<int>();
final String columnName;
String _raw;
DateTimeSqlExpressionBuilder(this.columnName);
@override
bool get hasValue =>
_raw?.isNotEmpty == true ||
year.hasValue ||
month.hasValue ||
day.hasValue ||
hour.hasValue ||
minute.hasValue ||
second.hasValue;
bool _change(String _op, DateTime dt, bool time) {
var dateString = time ? dateYmdHms.format(dt) : dateYmd.format(dt);
_raw = '$columnName $_op \'$dateString\'';
return true;
}
operator <(DateTime value) => _change('<', value, true);
operator <=(DateTime value) => _change('<=', value, true);
operator >(DateTime value) => _change('>', value, true);
operator >=(DateTime value) => _change('>=', value, true);
void equals(DateTime value, {bool includeTime: true}) {
_change('=', value, includeTime != false);
}
void lessThan(DateTime value, {bool includeTime: true}) {
_change('<', value, includeTime != false);
}
void lessThanOrEqualTo(DateTime value, {bool includeTime: true}) {
_change('<=', value, includeTime != false);
}
void greaterThan(DateTime value, {bool includeTime: true}) {
_change('>', value, includeTime != false);
}
void greaterThanOrEqualTo(DateTime value, {bool includeTime: true}) {
_change('>=', value, includeTime != false);
}
@override
void isIn(Iterable<DateTime> values) {
_raw = '$columnName IN (' +
values.map(dateYmdHms.format).map((s) => '$s').join(', ') +
')';
}
@override
void isNotIn(Iterable<DateTime> values) {
_raw = '$columnName NOT IN (' +
values.map(dateYmdHms.format).map((s) => '$s').join(', ') +
')';
}
@override
void isBetween(DateTime lower, DateTime upper) {
var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper);
_raw = "$columnName BETWEEN '$l' and '$u'";
}
@override
void isNotBetween(DateTime lower, DateTime upper) {
var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper);
_raw = "$columnName NOT BETWEEN '$l' and '$u'";
}
@override
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()}');
return parts.isEmpty ? null : parts.join(' AND ');
}
}

View file

@ -1,8 +1,8 @@
abstract class RelationshipType { abstract class RelationshipType {
static const int HAS_MANY = 0; static const int hasMany = 0;
static const int HAS_ONE = 1; static const int hasOne = 1;
static const int BELONGS_TO = 2; static const int belongsTo = 2;
static const int BELONGS_TO_MANY = 3; static const int belongsToMany = 3;
} }
class Relationship { class Relationship {
@ -25,7 +25,7 @@ class HasMany extends Relationship {
String foreignKey, String foreignKey,
String foreignTable, String foreignTable,
bool cascadeOnDelete: false}) bool cascadeOnDelete: false})
: super(RelationshipType.HAS_MANY, : super(RelationshipType.hasMany,
localKey: localKey, localKey: localKey,
foreignKey: foreignKey, foreignKey: foreignKey,
foreignTable: foreignTable, foreignTable: foreignTable,
@ -40,7 +40,7 @@ class HasOne extends Relationship {
String foreignKey, String foreignKey,
String foreignTable, String foreignTable,
bool cascadeOnDelete: false}) bool cascadeOnDelete: false})
: super(RelationshipType.HAS_ONE, : super(RelationshipType.hasOne,
localKey: localKey, localKey: localKey,
foreignKey: foreignKey, foreignKey: foreignKey,
foreignTable: foreignTable, foreignTable: foreignTable,
@ -52,7 +52,7 @@ const HasOne hasOne = const HasOne();
class BelongsTo extends Relationship { class BelongsTo extends Relationship {
const BelongsTo( const BelongsTo(
{String localKey: 'id', String foreignKey, String foreignTable}) {String localKey: 'id', String foreignKey, String foreignTable})
: super(RelationshipType.BELONGS_TO, : super(RelationshipType.belongsTo,
localKey: localKey, localKey: localKey,
foreignKey: foreignKey, foreignKey: foreignKey,
foreignTable: foreignTable); foreignTable: foreignTable);
@ -63,7 +63,7 @@ const BelongsTo belongsTo = const BelongsTo();
class BelongsToMany extends Relationship { class BelongsToMany extends Relationship {
const BelongsToMany( const BelongsToMany(
{String localKey: 'id', String foreignKey, String foreignTable}) {String localKey: 'id', String foreignKey, String foreignTable})
: super(RelationshipType.BELONGS_TO_MANY, : super(RelationshipType.belongsToMany,
localKey: localKey, localKey: localKey,
foreignKey: foreignKey, foreignKey: foreignKey,
foreignTable: foreignTable); foreignTable: foreignTable);

View file

@ -1,10 +1,13 @@
name: angel_orm name: angel_orm
version: 1.0.0-alpha+11 version: 2.0.0-dev
description: Runtime support for Angel's ORM. description: Runtime support for Angel's ORM.
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/orm homepage: https://github.com/angel-dart/orm
environment: environment:
sdk: '>=2.0.0-dev.1.2 <2.0.0' sdk: '>=2.0.0-dev.1.2 <3.0.0'
dependencies: dependencies:
angel_model: ^1.0.0 intl: ^0.15.7
meta: ^1.0.0 meta: ^1.0.0
string_scanner: ^1.0.0
dev_dependencies:
angel_model: ^1.0.0

View file

@ -1,4 +0,0 @@
export 'src/builder/orm/migration.dart';
export 'src/builder/orm/postgres.dart';
export 'src/builder/orm/service.dart';
export 'src/builder/orm/sql_migration.dart';

View file

@ -1,207 +0,0 @@
import 'dart:async';
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize_generator/build_context.dart' as serialize;
import 'package:angel_serialize_generator/context.dart' as serialize;
import 'package:build/build.dart';
import 'package:inflection/inflection.dart';
import 'package:recase/recase.dart';
import 'package:source_gen/source_gen.dart';
import 'postgres_build_context.dart';
const TypeChecker columnTypeChecker = const TypeChecker.fromRuntime(Column),
dateTimeTypeChecker = const TypeChecker.fromRuntime(DateTime),
ormTypeChecker = const TypeChecker.fromRuntime(ORM),
relationshipTypeChecker = const TypeChecker.fromRuntime(Relationship);
const TypeChecker hasOneTypeChecker = const TypeChecker.fromRuntime(HasOne),
hasManyTypeChecker = const TypeChecker.fromRuntime(HasMany),
belongsToTypeChecker = const TypeChecker.fromRuntime(BelongsTo),
belongsToManyTypeChecker = const TypeChecker.fromRuntime(BelongsToMany);
ColumnType inferColumnType(DartType type) {
if (const TypeChecker.fromRuntime(String).isAssignableFromType(type))
return ColumnType.VAR_CHAR;
if (const TypeChecker.fromRuntime(int).isAssignableFromType(type))
return ColumnType.INT;
if (const TypeChecker.fromRuntime(double).isAssignableFromType(type))
return ColumnType.DECIMAL;
if (const TypeChecker.fromRuntime(num).isAssignableFromType(type))
return ColumnType.NUMERIC;
if (const TypeChecker.fromRuntime(bool).isAssignableFromType(type))
return ColumnType.BOOLEAN;
if (const TypeChecker.fromRuntime(DateTime).isAssignableFromType(type))
return ColumnType.TIME_STAMP;
return null;
}
Column reviveColumn(ConstantReader cr) {
// TODO: Get index type, column type...
var args = cr.revive().namedArguments;
IndexType indexType = IndexType.NONE;
ColumnType columnType;
if (args.containsKey('index')) {
indexType = IndexType.values[args['index'].getField('index').toIntValue()];
}
if (args.containsKey('type')) {
columnType = new _ColumnType(args['type'].getField('name').toStringValue());
}
return new Column(
nullable: cr.peek('nullable')?.boolValue,
length: cr.peek('length')?.intValue,
defaultValue: cr.peek('defaultValue')?.literalValue,
type: columnType,
index: indexType,
);
}
ORM reviveOrm(ConstantReader cr) {
return new ORM(cr.peek('tableName')?.stringValue);
}
Relationship reviveRelationship(DartObject relationshipAnnotation) {
var cr = new ConstantReader(relationshipAnnotation);
var r = cr.revive().namedArguments;
int type = -1;
if (cr.instanceOf(hasOneTypeChecker))
type = RelationshipType.HAS_ONE;
else if (cr.instanceOf(hasManyTypeChecker))
type = RelationshipType.HAS_MANY;
else if (cr.instanceOf(belongsToTypeChecker))
type = RelationshipType.BELONGS_TO;
else if (cr.instanceOf(belongsToManyTypeChecker))
type = RelationshipType.BELONGS_TO_MANY;
else
throw new UnsupportedError(
'Unsupported relationship type "${relationshipAnnotation.type.name}".');
return new Relationship(type,
localKey: r['localKey']?.toStringValue(),
foreignKey: r['foreignKey']?.toStringValue(),
foreignTable: r['foreignTable']?.toStringValue(),
cascadeOnDelete: r['cascadeOnDelete']?.toBoolValue());
}
Future<PostgresBuildContext> buildContext(
ClassElement clazz,
ORM annotation,
BuildStep buildStep,
Resolver resolver,
bool autoSnakeCaseNames,
bool autoIdAndDateFields) async {
var raw = await serialize.buildContext(clazz, null, buildStep, resolver,
autoSnakeCaseNames != false, autoIdAndDateFields != false);
var ctx = await PostgresBuildContext.create(
clazz, raw, annotation, resolver, buildStep,
tableName: (annotation.tableName?.isNotEmpty == true)
? annotation.tableName
: pluralize(new ReCase(clazz.name).snakeCase),
autoSnakeCaseNames: autoSnakeCaseNames != false,
autoIdAndDateFields: autoIdAndDateFields != false);
List<String> fieldNames = [];
List<FieldElement> fields = [];
for (var field in raw.fields) {
fieldNames.add(field.name);
// Check for joins.
var canJoins = canJoinTypeChecker.annotationsOf(field);
for (var ann in canJoins) {
var cr = new ConstantReader(ann);
ctx.joins[field.name] ??= [];
ctx.joins[field.name].add(new JoinContext(
resolveModelAncestor(cr.read('type').typeValue),
cr.read('foreignKey').stringValue,
));
}
// Check for relationship. If so, skip.
var relationshipAnnotation =
relationshipTypeChecker.firstAnnotationOf(field);
if (relationshipAnnotation != null) {
ctx.relationshipFields.add(field);
ctx.relationships[field.name] =
reviveRelationship(relationshipAnnotation);
continue;
}
// Check for column annotation...
Column column;
var columnAnnotation = columnTypeChecker.firstAnnotationOf(field);
if (columnAnnotation != null) {
column = reviveColumn(new ConstantReader(columnAnnotation));
}
if (column == null && field.name == 'id' && ctx.shimmed['id'] == true) {
column = const Column(type: ColumnType.SERIAL);
}
if (column == null) {
// Guess what kind of column this is...
column = new Column(
type: inferColumnType(
field.type,
),
);
}
if (column != null && column.type == null) {
column = new Column(
nullable: column.nullable,
length: column.length,
index: column.index,
defaultValue: column.defaultValue,
type: inferColumnType(field.type),
);
}
if (column?.type == 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);
// Add belongs to fields
// TODO: Do this for belongs to many as well
ctx.relationships.forEach((name, r) {
var relationship = ctx.populateRelationship(name);
var rc = new ReCase(relationship.localKey);
if (relationship.type == RelationshipType.BELONGS_TO) {
ctx.fields.removeWhere((f) => f.name == rc.camelCase);
var field = new RelationshipConstraintField(
rc.camelCase, ctx.typeProvider.intType, name);
ctx.fields.add(field);
ctx.aliases[field.name] = relationship.localKey;
}
});
return ctx;
}
class RelationshipConstraintField extends FieldElementImpl {
@override
final DartType type;
final String originalName;
RelationshipConstraintField(String name, this.type, this.originalName)
: super(name, -1);
}
class _ColumnType implements ColumnType {
@override
final String name;
_ColumnType(this.name);
}

View file

@ -1,206 +0,0 @@
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:build/build.dart';
import 'package:code_builder/code_builder.dart';
import 'package:source_gen/source_gen.dart' hide LibraryBuilder;
import 'build_context.dart';
import 'postgres_build_context.dart';
import 'lib_core.dart' as lib$core;
class MigrationGenerator extends GeneratorForAnnotation<ORM> {
static final Parameter _schemaParam = new Parameter((b) {
b
..name = 'schema'
..type = new TypeReference((b) => b.symbol = 'Schema');
});
static final Expression _schema = new CodeExpression(new Code('schema'));
/// If `true` (default), then field names will automatically be (de)serialized as snake_case.
final bool autoSnakeCaseNames;
/// If `true` (default), then the schema will automatically add id, created_at and updated_at fields.
final bool autoIdAndDateFields;
const MigrationGenerator(
{this.autoSnakeCaseNames: true, this.autoIdAndDateFields: true});
@override
Future<String> generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) async {
if (buildStep.inputId.path.contains('.migration.g.dart')) {
return null;
}
if (element is! ClassElement)
throw 'Only classes can be annotated with @ORM().';
var resolver = await buildStep.resolver;
var ctx = await buildContext(element, reviveOrm(annotation), buildStep,
resolver, autoSnakeCaseNames != false, autoIdAndDateFields != false);
var lib = generateMigrationLibrary(ctx, element, resolver, buildStep);
if (lib == null) return null;
var emitter = new DartEmitter();
return lib.accept(emitter).toString();
}
Library generateMigrationLibrary(PostgresBuildContext ctx,
ClassElement element, Resolver resolver, BuildStep buildStep) {
return new Library((lib) {
lib.directives.add([
new Directive.import('package:angel_migration/angel_migration.dart'),
]);
lib.body.add(new Class((b) {
b.name = '${ctx.modelClassName}Migration';
b.extend = new Reference('Migration');
}));
lib.methods.add(buildUpMigration(ctx, lib));
lib.methods.add(buildDownMigration(ctx));
});
}
Method buildUpMigration(PostgresBuildContext ctx, LibraryBuilder lib) {
return new Method((meth) {
meth.name = 'up';
meth.annotations.add(lib$core.override);
meth.requiredParameters.add(_schemaParam);
var closure = new Method((closure) {
closure.requiredParameters.add(new Parameter((b) => b.name = 'table'));
var table = new Reference('table');
List<String> dup = [];
bool hasOrmImport = false;
ctx.columnInfo.forEach((name, col) {
var key = ctx.resolveFieldName(name);
if (dup.contains(key))
return;
else {
if (key != 'id' || autoIdAndDateFields == false) {
// Check for relationships that might duplicate
for (var rName in ctx.relationships.keys) {
var relationship = ctx.populateRelationship(rName);
if (relationship.localKey == key) return;
}
}
dup.add(key);
}
String methodName;
List<Expression> positional = [literal(key)];
Map<String, Expression> named = {};
if (autoIdAndDateFields != false && name == 'id') methodName = 'serial';
if (methodName == null) {
switch (col.type) {
case ColumnType.VAR_CHAR:
methodName = 'varchar';
if (col.length != null) named['length'] = literal(col.length);
break;
case ColumnType.SERIAL:
methodName = 'serial';
break;
case ColumnType.INT:
methodName = 'integer';
break;
case ColumnType.FLOAT:
methodName = 'float';
break;
case ColumnType.NUMERIC:
methodName = 'numeric';
break;
case ColumnType.BOOLEAN:
methodName = 'boolean';
break;
case ColumnType.DATE:
methodName = 'date';
break;
case ColumnType.DATE_TIME:
methodName = 'dateTime';
break;
case ColumnType.TIME_STAMP:
methodName = 'timeStamp';
break;
default:
if (!hasOrmImport) {
hasOrmImport = true;
lib.directives.add(new Directive.import('package:angel_orm/angel_orm.dart'));
}
Expression provColumn;
if (col.length == null) {
methodName = 'declare';
provColumn = new CodeExpression(new Code("new ColumnType('${col.type.name}')"));
} else {
methodName = 'declareColumn';
provColumn = new CodeExpression(new Code("new Column({type: new Column('${col.type.name}'), length: ${col.length})"));
}
positional.add(provColumn);
break;
}
}
var field = table.property(methodName).call(positional, named);
var cascade = <Expression Function(Expression)>[];
if (col.defaultValue != null) {
cascade
.add((e) => e.property('defaultsTo').call([literal(col.defaultValue)]));
}
if (col.index == IndexType.PRIMARY_KEY ||
(autoIdAndDateFields != false && name == 'id'))
cascade.add((e) => e.property('primaryKey').call([]));
else if (col.index == IndexType.UNIQUE)
cascade.add((e) => e.property('unique').call([]));
if (col.nullable != true) cascade.add((e) => e.property('notNull').call([]));
field = cascade.isEmpty
? field
: field.cascade((e) => cascade.map((f) => f(e)).toList());
closure.addStatement(field);
});
ctx.relationships.forEach((name, r) {
var relationship = ctx.populateRelationship(name);
if (relationship.isBelongsTo) {
var key = relationship.localKey;
var field = table.property('integer').call([literal(key)]);
// .references('user', 'id').onDeleteCascade()
var ref = field.property('references').call([
literal(relationship.foreignTable),
literal(relationship.foreignKey),
]);
if (relationship.cascadeOnDelete != false && relationship.isSingular)
ref = ref.property('onDeleteCascade').call([]);
return closure.addStatement(ref);
}
});
meth.addStatement(_schema.property('create').call([
literal(ctx.tableName),
closure,
]));
});
});
}
Method buildDownMigration(PostgresBuildContext ctx) {
return new Method((b) {
b.name = 'down';
b.requiredParameters.add(_schemaParam);
b.annotations.add(lib$core.override);
b.body.add(new Code("schema.drop('${ctx.tableName}')"));
});
}
}

View file

@ -1,318 +0,0 @@
import 'dart:async';
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:angel_model/angel_model.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize_generator/context.dart';
import 'package:build/build.dart';
import 'package:code_builder/code_builder.dart';
import 'package:inflection/inflection.dart';
import 'package:recase/recase.dart';
import 'package:source_gen/source_gen.dart';
import 'build_context.dart';
const TypeChecker canJoinTypeChecker = const TypeChecker.fromRuntime(CanJoin);
DartType resolveModelAncestor(DartType type) {
DartType refType = type;
while (refType != null) {
if (!const TypeChecker.fromRuntime(Model).isAssignableFromType(refType)) {
var parent = (refType.element as ClassElement).allSupertypes[0];
if (parent != refType)
refType = parent;
else
refType = null;
} else
break;
}
if (refType != null) return refType;
throw '${type.name} does not extend Model.';
}
class JoinContext {
final DartType type;
final String foreignKey;
JoinContext(this.type, this.foreignKey);
}
class PostgresBuildContext extends BuildContext {
LibraryElement _libraryCache;
TypeProvider _typeProviderCache;
TypeBuilder _modelClassBuilder,
_queryClassBuilder,
_whereClassBuilder,
_postgresqlConnectionBuilder;
String _prefix;
final Map<String, Relationship> _populatedRelationships = {};
final Map<String, Column> columnInfo = {};
final Map<String, IndexType> indices = {};
final Map<String, List<JoinContext>> joins = {};
final Map<String, Relationship> relationships = {};
final bool autoSnakeCaseNames, autoIdAndDateFields;
final String tableName;
final ORM ormAnnotation;
final ClassElement element;
final BuildContext raw;
final Resolver resolver;
final BuildStep buildStep;
ReCase _reCase;
String primaryKeyName = 'id';
PostgresBuildContext._(
this.element, this.raw, this.ormAnnotation, this.resolver, this.buildStep,
{this.tableName, this.autoSnakeCaseNames, this.autoIdAndDateFields})
: super(raw.annotation,
originalClassName: raw.originalClassName,
sourceFilename: raw.sourceFilename);
static Future<PostgresBuildContext> create(
ClassElement element,
BuildContext raw,
ORM ormAnnotation,
Resolver resolver,
BuildStep buildStep,
{String tableName,
bool autoSnakeCaseNames,
bool autoIdAndDateFields}) async {
var ctx = new PostgresBuildContext._(
element,
raw,
ormAnnotation,
resolver,
buildStep,
tableName: tableName,
autoSnakeCaseNames: autoSnakeCaseNames,
autoIdAndDateFields: autoIdAndDateFields,
);
// Library
ctx._libraryCache = await resolver.libraryFor(buildStep.inputId);
return ctx;
}
final List<FieldElement> fields = [], relationshipFields = [];
ReCase get reCase => _reCase ?? new ReCase(modelClassName);
TypeBuilder get modelClassBuilder =>
_modelClassBuilder ??= new TypeBuilder(modelClassName);
TypeBuilder get queryClassBuilder =>
_queryClassBuilder ??= new TypeBuilder(queryClassName);
TypeBuilder get whereClassBuilder =>
_whereClassBuilder ??= new TypeBuilder(whereClassName);
TypeBuilder get postgreSQLConnectionBuilder =>
_postgresqlConnectionBuilder ??= new TypeBuilder('PostgreSQLConnection');
String get prefix {
if (_prefix != null) return _prefix;
if (relationships.isEmpty)
return _prefix = '';
else
return _prefix = tableName + '.';
}
Map<String, String> get aliases => raw.aliases;
Map<String, bool> get shimmed => raw.shimmed;
String get sourceFilename => raw.sourceFilename;
String get modelClassName => raw.modelClassName;
String get originalClassName => raw.originalClassName;
String get queryClassName => modelClassName + 'Query';
String get whereClassName => queryClassName + 'Where';
LibraryElement get library => _libraryCache;
TypeProvider get typeProvider =>
_typeProviderCache ??= library.context.typeProvider;
FieldElement resolveRelationshipField(String name) =>
relationshipFields.firstWhere((f) => f.name == name, orElse: () => null);
PopulatedRelationship populateRelationship(String name) {
return _populatedRelationships.putIfAbsent(name, () {
var f = raw.fields.firstWhere((f) => f.name == name);
var relationship = relationships[name];
DartType refType = f.type;
if (refType.isAssignableTo(typeProvider.listType) ||
refType.name == 'List') {
var iType = refType as InterfaceType;
if (iType.typeArguments.isEmpty)
throw 'Relationship "${f.name}" cannot be modeled as a generic List.';
refType = iType.typeArguments.first;
}
var typeName = refType.name.startsWith('_')
? refType.name.substring(1)
: refType.name;
var rc = new ReCase(typeName);
if (relationship.type == RelationshipType.HAS_ONE ||
relationship.type == RelationshipType.HAS_MANY) {
//print('Has many $tableName');
var single = singularize(tableName);
var foreignKey = relationship.foreignTable ??
(autoSnakeCaseNames != false ? '${single}_id' : '${single}Id');
var localKey = relationship.localKey ?? 'id';
var foreignTable = relationship.foreignTable ??
(autoSnakeCaseNames != false
? pluralize(rc.snakeCase)
: pluralize(typeName));
return new PopulatedRelationship(
relationship.type,
f.name,
f.type,
buildStep,
resolver,
autoSnakeCaseNames,
autoIdAndDateFields,
relationship.type == RelationshipType.HAS_ONE,
typeProvider,
localKey: localKey,
foreignKey: foreignKey,
foreignTable: foreignTable,
cascadeOnDelete: relationship.cascadeOnDelete);
} else if (relationship.type == RelationshipType.BELONGS_TO ||
relationship.type == RelationshipType.BELONGS_TO_MANY) {
var localKey = relationship.localKey ??
(autoSnakeCaseNames != false
? '${rc.snakeCase}_id'
: '${typeName}Id');
var foreignKey = relationship.foreignKey ?? 'id';
var foreignTable = relationship.foreignTable ??
(autoSnakeCaseNames != false
? pluralize(rc.snakeCase)
: pluralize(typeName));
return new PopulatedRelationship(
relationship.type,
f.name,
f.type,
buildStep,
resolver,
autoSnakeCaseNames,
autoIdAndDateFields,
relationship.type == RelationshipType.BELONGS_TO,
typeProvider,
localKey: localKey,
foreignKey: foreignKey,
foreignTable: foreignTable,
cascadeOnDelete: relationship.cascadeOnDelete);
} else
throw new UnsupportedError(
'Invalid relationship type: ${relationship.type}');
});
}
@override
String toString() {
return 'PostgresBuildContext: $originalClassName';
}
}
class PopulatedRelationship extends Relationship {
bool _isList;
DartType _modelType;
PostgresBuildContext _modelTypeContext;
DartObject _modelTypeORM;
final String originalName;
final DartType dartType;
final BuildStep buildStep;
final Resolver resolver;
final bool autoSnakeCaseNames, autoIdAndDateFields;
final bool isSingular;
final TypeProvider typeProvider;
PopulatedRelationship(
int type,
this.originalName,
this.dartType,
this.buildStep,
this.resolver,
this.autoSnakeCaseNames,
this.autoIdAndDateFields,
this.isSingular,
this.typeProvider,
{String localKey,
String foreignKey,
String foreignTable,
bool cascadeOnDelete})
: super(type,
localKey: localKey,
foreignKey: foreignKey,
foreignTable: foreignTable,
cascadeOnDelete: cascadeOnDelete);
bool get isBelongsTo =>
type == RelationshipType.BELONGS_TO ||
type == RelationshipType.BELONGS_TO_MANY;
bool get isHas =>
type == RelationshipType.HAS_ONE || type == RelationshipType.HAS_MANY;
bool get isList => _isList ??=
dartType.isAssignableTo(typeProvider.listType) || dartType.name == 'List';
DartType get modelType {
if (_modelType != null) return _modelType;
DartType searchType = dartType;
var ormChecker = new TypeChecker.fromRuntime(ORM);
// Get inner type from List if any...
if (!isSingular) {
if (!isList)
throw '"$originalName" is a many-to-one relationship, and thus it should be represented as a List within your Dart class. You have it represented as ${dartType.name}.';
else {
var iType = dartType as InterfaceType;
if (iType.typeArguments.isEmpty)
throw '"$originalName" is a many-to-one relationship, and should be modeled as a List that references another model type. Example: `List<T>`, where T is a model type.';
else
searchType = iType.typeArguments.first;
}
}
while (searchType != null) {
var classElement = searchType.element as ClassElement;
var ormAnnotation = ormChecker.firstAnnotationOf(classElement);
if (ormAnnotation != null) {
_modelTypeORM = ormAnnotation;
return _modelType = searchType;
} else {
// If we didn't find an @ORM(), then refer to the parent type.
searchType = classElement.supertype;
}
}
throw new StateError(
'Neither ${dartType.name} nor its parent types are annotated with an @ORM() annotation. It is impossible to compute this relationship.');
}
Future<PostgresBuildContext> get modelTypeContext async {
if (_modelTypeContext != null) return _modelTypeContext;
var reader = new ConstantReader(_modelTypeORM);
if (reader.isNull)
reader = null;
else
reader = reader.read('tableName');
var orm = reader == null
? new ORM()
: new ORM(reader.isString ? reader.stringValue : null);
return _modelTypeContext = await buildContext(modelType.element, orm,
buildStep, resolver, autoSnakeCaseNames, autoIdAndDateFields);
}
}

View file

@ -1,399 +0,0 @@
import 'dart:async';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:build/build.dart';
import 'package:code_builder/code_builder.dart';
import 'package:path/path.dart' as p;
import 'package:recase/recase.dart';
import 'package:source_gen/source_gen.dart' hide LibraryBuilder;
import 'build_context.dart';
import 'postgres_build_context.dart';
class PostgresServiceGenerator extends GeneratorForAnnotation<ORM> {
static const List<TypeChecker> primitives = const [
const TypeChecker.fromRuntime(String),
const TypeChecker.fromRuntime(int),
const TypeChecker.fromRuntime(bool),
const TypeChecker.fromRuntime(double),
const TypeChecker.fromRuntime(num),
];
static final ExpressionBuilder id = reference('id'),
params = reference('params'),
connection = reference('connection'),
query = reference('query'),
buildQuery = reference('buildQuery'),
applyData = reference('applyData'),
where = reference('query').property('where'),
toId = reference('toId'),
data = reference('data');
final bool autoSnakeCaseNames;
final bool autoIdAndDateFields;
const PostgresServiceGenerator(
{this.autoSnakeCaseNames: true, this.autoIdAndDateFields: true});
@override
Future<String> generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) async {
if (buildStep.inputId.path.contains('.service.g.dart')) {
return null;
}
if (element is! ClassElement)
throw 'Only classes can be annotated with @ORM().';
var resolver = await buildStep.resolver;
var lib = await generateOrmLibrary(element.library, resolver, buildStep)
.then((l) => l.buildAst());
if (lib == null) return null;
return prettyToSource(lib);
}
Future<LibraryBuilder> generateOrmLibrary(LibraryElement libraryElement,
Resolver resolver, BuildStep buildStep) async {
var lib = new LibraryBuilder();
lib.addDirective(new ImportBuilder('dart:async'));
lib.addDirective(
new ImportBuilder('package:angel_framework/angel_framework.dart'));
lib.addDirective(new ImportBuilder('package:postgres/postgres.dart'));
lib.addDirective(new ImportBuilder(p.basename(buildStep.inputId.path)));
var pathName = p.basenameWithoutExtension(
p.basenameWithoutExtension(buildStep.inputId.path));
lib.addDirective(new ImportBuilder('$pathName.orm.g.dart'));
var elements = libraryElement.definingCompilationUnit.unit.declarations
.where((el) => el is ClassDeclaration);
Map<ClassElement, PostgresBuildContext> contexts = {};
List<String> done = [];
for (ClassDeclaration element in elements) {
if (!done.contains(element.name)) {
var ann = ormTypeChecker.firstAnnotationOf(element.element);
if (ann != null) {
contexts[element.element] = await buildContext(
element.element,
reviveOrm(new ConstantReader(ann)),
buildStep,
resolver,
autoSnakeCaseNames != false,
autoIdAndDateFields != false);
}
}
}
if (contexts.isEmpty) return null;
done.clear();
for (var element in contexts.keys) {
if (!done.contains(element.name)) {
var ctx = contexts[element];
lib.addMember(buildServiceClass(ctx));
done.add(element.name);
}
}
return lib;
}
ClassBuilder buildServiceClass(PostgresBuildContext ctx) {
var rc = new ReCase(ctx.modelClassName);
var clazz = new ClassBuilder('${rc.pascalCase}Service',
asExtends: new TypeBuilder('Service'));
// Add fields
// connection, allowRemoveAll, allowQuery
clazz
..addField(varFinal('connection', type: ctx.postgreSQLConnectionBuilder))
..addField(varFinal('allowRemoveAll', type: lib$core.bool))
..addField(varFinal('allowQuery', type: lib$core.bool));
clazz.addConstructor(constructor([
thisField(parameter('connection')),
thisField(named(parameter('allowRemoveAll', [literal(false)]))),
thisField(named(parameter('allowQuery', [literal(false)])))
]));
clazz.addMethod(buildQueryMethod(ctx));
clazz.addMethod(buildToIdMethod(ctx));
clazz.addMethod(buildApplyDataMethod(ctx));
clazz.addMethod(buildIndexMethod(ctx));
clazz.addMethod(buildCreateMethod(ctx));
clazz.addMethod(buildReadOrDeleteMethod('read', 'get', ctx));
clazz.addMethod(buildReadOrDeleteMethod('remove', 'delete', ctx));
clazz.addMethod(buildUpdateMethod(ctx));
clazz.addMethod(buildModifyMethod(ctx));
return clazz;
}
MethodBuilder buildQueryMethod(PostgresBuildContext ctx) {
var meth =
new MethodBuilder('buildQuery', returnType: ctx.queryClassBuilder)
..addPositional(parameter('params', [lib$core.Map]));
var paramQuery = params[literal('query')];
meth.addStatement(
varField('query', value: ctx.queryClassBuilder.newInstance([])));
var ifStmt = ifThen(paramQuery.isInstanceOf(lib$core.Map));
ctx.fields.forEach((f) {
var alias = ctx.resolveFieldName(f.name);
var queryKey = paramQuery[literal(alias)];
if (f.type.isDynamic ||
f.type.isObject ||
f.type.isObject ||
primitives.any((t) => t.isAssignableFromType(f.type))) {
ifStmt
.addStatement(where.property(f.name).invoke('equals', [queryKey]));
} else if (dateTimeTypeChecker.isAssignableFromType(f.type)) {
var dt = queryKey
.isInstanceOf(lib$core.String)
.ternary(lib$core.DateTime.invoke('parse', [queryKey]), queryKey);
ifStmt.addStatement(
where.property(f.name).invoke('equals', [updatedAt(dt)]));
} else {
print(
'Cannot compute service query binding for field "${f.name}" in ${ctx.originalClassName}');
}
});
meth.addStatement(ifStmt);
meth.addStatement(query.asReturn());
return meth;
}
MethodBuilder buildToIdMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('toId', returnType: lib$core.int)
..addPositional(parameter('id'));
meth.addStatement(ifThen(id.isInstanceOf(lib$core.int), [
id.asReturn(),
elseThen([
ifThen(id.equals(literal('null')).or(id.equals(literal(null))), [
literal(null).asReturn(),
elseThen([
lib$core.int.invoke('parse', [id.invoke('toString', [])]).asReturn()
])
])
])
]));
return meth;
}
MethodBuilder buildIndexMethod(PostgresBuildContext ctx) {
// Future<List<T>> index([p]) => buildQuery(p).get(connection).toList();
return method('index', [
new TypeBuilder('Future', genericTypes: [
new TypeBuilder('List', genericTypes: [ctx.modelClassBuilder])
]),
parameter('params', [lib$core.Map]).asOptional(),
reference('buildQuery').call([params]).invoke('get', [connection]).invoke(
'toList',
[],
).asReturn(),
]);
}
MethodBuilder buildReadOrDeleteMethod(
String name, String operation, PostgresBuildContext ctx) {
var throw404 = new MethodBuilder.closure()..addPositional(parameter('_'));
throw404.addStatement(new TypeBuilder('AngelHttpException').newInstance(
[],
constructor: 'notFound',
named: {
'message':
literal('No record found for ID ') + id.invoke('toString', []),
},
));
return method(name, [
new TypeBuilder('Future', genericTypes: [ctx.modelClassBuilder]),
parameter('id'),
parameter('params', [lib$core.Map]).asOptional(),
varField('query', value: buildQuery.call([params])),
where.property('id').invoke('equals', [
toId.call([id])
]),
query
.invoke(operation, [connection])
.property('first')
.invoke('catchError', [
throw404,
])
.asReturn(),
]);
}
MethodBuilder buildApplyDataMethod(PostgresBuildContext ctx) {
var meth =
new MethodBuilder('applyData', returnType: ctx.modelClassBuilder);
meth.addPositional(parameter('data'));
meth.addStatement(ifThen(
data.isInstanceOf(ctx.modelClassBuilder).or(data.equals(literal(null))),
[
data.asReturn(),
],
));
var ifStmt = new IfStatementBuilder(data.isInstanceOf(lib$core.Map));
ifStmt.addStatement(
varField('query', value: ctx.modelClassBuilder.newInstance([])));
applyFieldsToInstance(ctx, query, ifStmt.addStatement);
ifStmt.addStatement(query.asReturn());
ifStmt.setElse(
new TypeBuilder('AngelHttpException')
.newInstance([],
constructor: 'badRequest',
named: {'message': literal('Invalid data.')})
.asThrow(),
);
meth.addStatement(ifStmt);
return meth;
}
MethodBuilder buildCreateMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('create',
returnType:
new TypeBuilder('Future', genericTypes: [ctx.modelClassBuilder]));
meth
..addPositional(parameter('data'))
..addPositional(parameter('params', [lib$core.Map]).asOptional());
var rc = new ReCase(ctx.modelClassName);
meth.addStatement(
ctx.queryClassBuilder.invoke('insert${rc.pascalCase}', [
connection,
applyData.call([data])
]).asReturn(),
);
return meth;
}
MethodBuilder buildModifyMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('modify',
modifier: MethodModifier.asAsync,
returnType:
new TypeBuilder('Future', genericTypes: [ctx.modelClassBuilder]));
meth
..addPositional(parameter('id'))
..addPositional(parameter('data'))
..addPositional(parameter('params', [lib$core.Map]).asOptional());
// read() by id
meth.addStatement(varField(
'query',
value: reference('read').call(
[
toId.call([id]),
params
],
).asAwait(),
));
var rc = new ReCase(ctx.modelClassName);
meth.addStatement(ifThen(data.isInstanceOf(ctx.modelClassBuilder), [
data.asAssign(query),
]));
var ifStmt = ifThen(data.isInstanceOf(lib$core.Map));
applyFieldsToInstance(ctx, query, ifStmt.addStatement);
meth.addStatement(ifStmt);
meth.addStatement(
ctx.queryClassBuilder
.invoke('update${rc.pascalCase}', [connection, query])
.asAwait()
.asReturn(),
);
return meth;
}
MethodBuilder buildUpdateMethod(PostgresBuildContext ctx) {
var meth = new MethodBuilder('update',
returnType:
new TypeBuilder('Future', genericTypes: [ctx.modelClassBuilder]));
meth
..addPositional(parameter('id'))
..addPositional(parameter('data'))
..addPositional(parameter('params', [lib$core.Map]).asOptional());
var rc = new ReCase(ctx.modelClassName);
meth.addStatement(
ctx.queryClassBuilder.invoke('update${rc.pascalCase}', [
connection,
applyData.call([data])
]).asReturn(),
);
return meth;
}
void parseParams(MethodBuilder meth, PostgresBuildContext ctx, {bool id}) {
meth.addStatement(varField('query',
value: buildQuery.call([
reference('params')
.notEquals(literal(null))
.ternary(reference('params'), map({}))
])));
if (id == true) {
meth.addStatement(
reference('query').property('where').property('id').invoke('equals', [
reference('toId').call([reference('id')])
]));
}
}
void applyFieldsToInstance(PostgresBuildContext ctx, ExpressionBuilder query,
void addStatement(StatementBuilder statement)) {
ctx.fields.forEach((f) {
var alias = ctx.resolveFieldName(f.name);
var dataKey = data[literal(alias)];
ExpressionBuilder target;
// Skip `id`
if (autoIdAndDateFields != false && f.name == 'id') return;
if (f.type.isDynamic ||
f.type.isObject ||
primitives.any((t) => t.isAssignableFromType(f.type))) {
target = dataKey;
} else if (dateTimeTypeChecker.isAssignableFromType(f.type)) {
var dt = dataKey
.isInstanceOf(lib$core.String)
.ternary(lib$core.DateTime.invoke('parse', [dataKey]), dataKey);
target = updatedAt(dt);
} else {
print(
'Cannot compute service applyData() binding for field "${f.name}" in ${ctx.originalClassName}');
}
if (target != null) {
addStatement(ifThen(data.invoke('containsKey', [literal(alias)]),
[target.asAssign(query.property(f.name))]));
}
});
}
ExpressionBuilder updatedAt(ExpressionBuilder dt) {
if (autoIdAndDateFields == false) return dt;
return dt
.notEquals(literal(null))
.ternary(dt, lib$core.DateTime.newInstance([], constructor: 'now'));
}
}

View file

@ -8,6 +8,6 @@ part 'author.g.dart';
@serializable @serializable
@orm @orm
class _Author extends Model { class _Author extends Model {
@Column(length: 255, index: IndexType.UNIQUE, defaultValue: 'Tobe Osakwe') @Column(length: 255, indexType: IndexType.UNIQUE, defaultValue: 'Tobe Osakwe')
String name; String name;
} }

View file

@ -9,7 +9,7 @@ part 'tree.g.dart';
@serializable @serializable
@orm @orm
class _Tree extends Model { class _Tree extends Model {
@Column(index: IndexType.UNIQUE, type: ColumnType.SMALL_INT) @Column(indexType: IndexType.UNIQUE, type: ColumnType.smallInt)
int rings; int rings;
@hasMany @hasMany

View file

@ -28,9 +28,9 @@ main() {
'Mazda', 'Mazda',
'CX9', 'CX9',
true, true,
DATE_YMD_HMS.format(MILENNIUM), dateYmdHms.format(MILENNIUM),
DATE_YMD_HMS.format(MILENNIUM), dateYmdHms.format(MILENNIUM),
DATE_YMD_HMS.format(MILENNIUM) dateYmdHms.format(MILENNIUM)
]; ];
print(row); print(row);
var car = CarQuery.parseRow(row); var car = CarQuery.parseRow(row);
@ -167,7 +167,7 @@ main() {
expect(car.description, 'Hello'); expect(car.description, 'Hello');
expect(car.familyFriendly, isTrue); expect(car.familyFriendly, isTrue);
expect( expect(
DATE_YMD_HMS.format(car.recalledAt), DATE_YMD_HMS.format(recalledAt)); dateYmdHms.format(car.recalledAt), dateYmdHms.format(recalledAt));
expect(car.createdAt, allOf(isNotNull, equals(car.updatedAt))); expect(car.createdAt, allOf(isNotNull, equals(car.updatedAt)));
}); });
@ -183,8 +183,8 @@ main() {
expect(car.make, beetle.make); expect(car.make, beetle.make);
expect(car.description, beetle.description); expect(car.description, beetle.description);
expect(car.familyFriendly, beetle.familyFriendly); expect(car.familyFriendly, beetle.familyFriendly);
expect(DATE_YMD_HMS.format(car.recalledAt), expect(dateYmdHms.format(car.recalledAt),
DATE_YMD_HMS.format(beetle.recalledAt)); dateYmdHms.format(beetle.recalledAt));
}); });
}); });
} }