update for new serializer

This commit is contained in:
Tobe O 2019-01-10 19:25:23 -05:00
parent 47606e1d51
commit 5ed22259c3
12 changed files with 270 additions and 47 deletions

View file

@ -14,6 +14,7 @@ class Orm {
const Orm({this.tableName, this.generateMigrations: true}); const Orm({this.tableName, this.generateMigrations: true});
} }
@deprecated
class Join { class Join {
final Type against; final Type against;
final String foreignKey; final String foreignKey;

View file

@ -399,3 +399,39 @@ class DateTimeSqlExpressionBuilder extends SqlExpressionBuilder<DateTime> {
return parts.isEmpty ? null : parts.join(' AND '); return parts.isEmpty ? null : parts.join(' AND ');
} }
} }
class MapSqlExpressionBuilder<K, V, Key extends SqlExpressionBuilder<K>,
Value extends SqlExpressionBuilder<V>> extends SqlExpressionBuilder {
final Key key;
final Value value;
bool _hasValue = false;
String _raw;
MapSqlExpressionBuilder(Query query, String columnName, this.key, this.value)
: super(query, columnName);
UnsupportedError _unsupported() =>
UnsupportedError('JSON/JSONB does not support this operation.');
@override
String compile() {
var parts = <String>[_raw, key.compile(), value.compile()];
parts.removeWhere((s) => s == null);
return parts.isEmpty ? null : parts.join(' && ');
}
@override
bool get hasValue => key.hasValue || value.hasValue || _hasValue;
@override
void isBetween(lower, upper) => throw _unsupported();
@override
void isIn(Iterable values) => throw _unsupported();
@override
void isNotBetween(lower, upper) => throw _unsupported();
@override
void isNotIn(Iterable values) => throw _unsupported();
}

View file

@ -111,6 +111,10 @@ class ColumnType {
static const ColumnType varBinaryMax = const ColumnType('varbinary(max)'); static const ColumnType varBinaryMax = const ColumnType('varbinary(max)');
static const ColumnType image = const ColumnType('image'); static const ColumnType image = const ColumnType('image');
// JSON.
static const ColumnType json = const ColumnType('json');
static const ColumnType jsonb = const ColumnType('jsonb');
// Misc. // Misc.
static const ColumnType sqlVariant = const ColumnType('sql_variant'); static const ColumnType sqlVariant = const ColumnType('sql_variant');
static const ColumnType uniqueIdentifier = static const ColumnType uniqueIdentifier =

View file

@ -26,12 +26,14 @@ targets:
- test/models/customer.dart - test/models/customer.dart
- test/models/foot.dart - test/models/foot.dart
- test/models/fruit.dart - test/models/fruit.dart
- test/models/has_map.dart
- test/models/role.dart - test/models/role.dart
$default: $default:
dependencies: dependencies:
- :_standalone - :_standalone
sources: sources:
- test/models/book.dart - test/models/book.dart
# - test/models/has_car.dart
- test/models/leg.dart - test/models/leg.dart
- test/models/order.dart - test/models/order.dart
- test/models/tree.dart - test/models/tree.dart

View file

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/element.dart';
import 'package:angel_model/angel_model.dart';
import 'package:angel_orm/angel_orm.dart'; import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize_generator/angel_serialize_generator.dart'; import 'package:angel_serialize_generator/angel_serialize_generator.dart';
import 'package:build/build.dart'; import 'package:build/build.dart';
@ -11,8 +12,7 @@ import 'orm_build_context.dart';
Builder migrationBuilder(BuilderOptions options) { Builder migrationBuilder(BuilderOptions options) {
return new SharedPartBuilder([ return new SharedPartBuilder([
new MigrationGenerator( new MigrationGenerator(
autoSnakeCaseNames: options.config['auto_snake_case_names'] != false, autoSnakeCaseNames: options.config['auto_snake_case_names'] != false)
autoIdAndDateFields: options.config['auto_id_and_date_fields'] != false)
], 'angel_migration'); ], 'angel_migration');
} }
@ -25,11 +25,7 @@ class MigrationGenerator extends GeneratorForAnnotation<Orm> {
/// If `true` (default), then field names will automatically be (de)serialized as snake_case. /// If `true` (default), then field names will automatically be (de)serialized as snake_case.
final bool autoSnakeCaseNames; final bool autoSnakeCaseNames;
/// If `true` (default), then the schema will automatically add id, created_at and updated_at fields. const MigrationGenerator({this.autoSnakeCaseNames: true});
final bool autoIdAndDateFields;
const MigrationGenerator(
{this.autoSnakeCaseNames: true, this.autoIdAndDateFields: true});
@override @override
Future<String> generateForAnnotatedElement( Future<String> generateForAnnotatedElement(
@ -45,13 +41,8 @@ class MigrationGenerator extends GeneratorForAnnotation<Orm> {
} }
var resolver = await buildStep.resolver; var resolver = await buildStep.resolver;
var ctx = await buildOrmContext( var ctx = await buildOrmContext(element as ClassElement, annotation,
element as ClassElement, buildStep, resolver, autoSnakeCaseNames != false);
annotation,
buildStep,
resolver,
autoSnakeCaseNames != false,
autoIdAndDateFields != false);
var lib = generateMigrationLibrary( var lib = generateMigrationLibrary(
ctx, element as ClassElement, resolver, buildStep); ctx, element as ClassElement, resolver, buildStep);
if (lib == null) return null; if (lib == null) return null;
@ -73,6 +64,8 @@ class MigrationGenerator extends GeneratorForAnnotation<Orm> {
Method buildUpMigration(OrmBuildContext ctx, LibraryBuilder lib) { Method buildUpMigration(OrmBuildContext ctx, LibraryBuilder lib) {
return new Method((meth) { return new Method((meth) {
var autoIdAndDateFields = const TypeChecker.fromRuntime(Model)
.isAssignableFromType(ctx.buildContext.clazz.type);
meth meth
..name = 'up' ..name = 'up'
..annotations.add(refer('override')) ..annotations.add(refer('override'))

View file

@ -27,7 +27,6 @@ Future<OrmBuildContext> buildOrmContext(
BuildStep buildStep, BuildStep buildStep,
Resolver resolver, Resolver resolver,
bool autoSnakeCaseNames, bool autoSnakeCaseNames,
bool autoIdAndDateFields,
{bool heedExclude: true}) async { {bool heedExclude: true}) async {
// Check for @generatedSerializable // Check for @generatedSerializable
// ignore: unused_local_variable // ignore: unused_local_variable
@ -44,8 +43,8 @@ Future<OrmBuildContext> buildOrmContext(
if (_cache.containsKey(id)) { if (_cache.containsKey(id)) {
return _cache[id]; return _cache[id];
} }
var buildCtx = await buildContext(clazz, annotation, buildStep, resolver, var buildCtx = await buildContext(
autoSnakeCaseNames, autoIdAndDateFields, clazz, annotation, buildStep, resolver, autoSnakeCaseNames,
heedExclude: heedExclude); heedExclude: heedExclude);
var ormAnnotation = reviveORMAnnotation(annotation); var ormAnnotation = reviveORMAnnotation(annotation);
var ctx = new OrmBuildContext( var ctx = new OrmBuildContext(
@ -66,7 +65,10 @@ Future<OrmBuildContext> buildOrmContext(
column = reviveColumn(new ConstantReader(columnAnnotation)); column = reviveColumn(new ConstantReader(columnAnnotation));
} }
if (column == null && field.name == 'id' && autoIdAndDateFields == true) { if (column == null &&
field.name == 'id' &&
const TypeChecker.fromRuntime(Model)
.isAssignableFromType(buildCtx.clazz.type)) {
// This is only for PostgreSQL, so implementations without a `serial` type // This is only for PostgreSQL, so implementations without a `serial` type
// must handle it accordingly, of course. // must handle it accordingly, of course.
column = const Column(type: ColumnType.serial); column = const Column(type: ColumnType.serial);
@ -76,7 +78,7 @@ Future<OrmBuildContext> buildOrmContext(
// Guess what kind of column this is... // Guess what kind of column this is...
column = new Column( column = new Column(
type: inferColumnType( type: inferColumnType(
field.type, buildCtx.resolveSerializedFieldType(field.name),
), ),
); );
} }
@ -133,8 +135,7 @@ Future<OrmBuildContext> buildOrmContext(
.firstAnnotationOf(modelType.element)), .firstAnnotationOf(modelType.element)),
buildStep, buildStep,
resolver, resolver,
autoSnakeCaseNames, autoSnakeCaseNames);
autoIdAndDateFields);
var ormAnn = const TypeChecker.fromRuntime(Orm) var ormAnn = const TypeChecker.fromRuntime(Orm)
.firstAnnotationOf(modelType.element); .firstAnnotationOf(modelType.element);
@ -176,7 +177,9 @@ Future<OrmBuildContext> buildOrmContext(
ctx.buildContext.aliases[name] = relation.localKey; ctx.buildContext.aliases[name] = relation.localKey;
if (!ctx.effectiveFields.any((f) => f.name == field.name)) { if (!ctx.effectiveFields.any((f) => f.name == field.name)) {
if (field.name != 'id' || !autoIdAndDateFields) { if (field.name != 'id' ||
!const TypeChecker.fromRuntime(Model)
.isAssignableFromType(ctx.buildContext.clazz.type)) {
var rf = new RelationFieldImpl(name, var rf = new RelationFieldImpl(name,
field.type.element.context.typeProvider.intType, field.name); field.type.element.context.typeProvider.intType, field.name);
ctx.effectiveFields.add(rf); ctx.effectiveFields.add(rf);
@ -212,6 +215,8 @@ ColumnType inferColumnType(DartType type) {
return ColumnType.boolean; return ColumnType.boolean;
if (const TypeChecker.fromRuntime(DateTime).isAssignableFromType(type)) if (const TypeChecker.fromRuntime(DateTime).isAssignableFromType(type))
return ColumnType.timeStamp; return ColumnType.timeStamp;
if (const TypeChecker.fromRuntime(Map).isAssignableFromType(type))
return ColumnType.jsonb;
return null; return null;
} }

View file

@ -1,5 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:angel_model/angel_model.dart';
import 'package:angel_orm/angel_orm.dart'; import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize_generator/angel_serialize_generator.dart'; import 'package:angel_serialize_generator/angel_serialize_generator.dart';
import 'package:angel_serialize_generator/build_context.dart'; import 'package:angel_serialize_generator/build_context.dart';
@ -12,8 +14,7 @@ import 'orm_build_context.dart';
Builder ormBuilder(BuilderOptions options) { Builder ormBuilder(BuilderOptions options) {
return new SharedPartBuilder([ return new SharedPartBuilder([
new OrmGenerator( new OrmGenerator(
autoSnakeCaseNames: options.config['auto_snake_case_names'] != false, autoSnakeCaseNames: options.config['auto_snake_case_names'] != false)
autoIdAndDateFields: options.config['auto_id_and_date_fields'] != false)
], 'angel_orm'); ], 'angel_orm');
} }
@ -26,16 +27,15 @@ TypeReference futureOf(String type) {
/// Builder that generates `.orm.g.dart`, with an abstract `FooOrm` class. /// Builder that generates `.orm.g.dart`, with an abstract `FooOrm` class.
class OrmGenerator extends GeneratorForAnnotation<Orm> { class OrmGenerator extends GeneratorForAnnotation<Orm> {
final bool autoSnakeCaseNames; final bool autoSnakeCaseNames;
final bool autoIdAndDateFields;
OrmGenerator({this.autoSnakeCaseNames, this.autoIdAndDateFields}); OrmGenerator({this.autoSnakeCaseNames});
@override @override
Future<String> generateForAnnotatedElement( Future<String> generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) async { Element element, ConstantReader annotation, BuildStep buildStep) async {
if (element is ClassElement) { if (element is ClassElement) {
var ctx = await buildOrmContext(element, annotation, buildStep, var ctx = await buildOrmContext(element, annotation, buildStep,
buildStep.resolver, autoSnakeCaseNames, autoIdAndDateFields); buildStep.resolver, autoSnakeCaseNames);
var lib = buildOrmLibrary(buildStep.inputId, ctx); var lib = buildOrmLibrary(buildStep.inputId, ctx);
return lib.accept(new DartEmitter()).toString(); return lib.accept(new DartEmitter()).toString();
} else { } else {
@ -146,10 +146,10 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
for (var field in ctx.effectiveFields) { for (var field in ctx.effectiveFields) {
Reference type = convertTypeReference(field.type); Reference type = convertTypeReference(field.type);
if (isSpecialId(field)) type = refer('int'); if (isSpecialId(ctx, field)) type = refer('int');
var expr = (refer('row').index(literalNum(i++))); var expr = (refer('row').index(literalNum(i++)));
if (isSpecialId(field)) if (isSpecialId(ctx, field))
expr = expr.property('toString').call([]); expr = expr.property('toString').call([]);
else if (field is RelationFieldImpl) else if (field is RelationFieldImpl)
continue; continue;
@ -229,7 +229,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
relation.type == RelationshipType.hasMany) { relation.type == RelationshipType.hasMany) {
var foreign = ctx.relationTypes[relation]; var foreign = ctx.relationTypes[relation];
var additionalFields = foreign.effectiveFields var additionalFields = foreign.effectiveFields
.where((f) => f.name != 'id' || !isSpecialId(f)) .where((f) => f.name != 'id' || !isSpecialId(ctx, f))
.map((f) => literalString( .map((f) => literalString(
foreign.buildContext.resolveFieldName(f.name))); foreign.buildContext.resolveFieldName(f.name)));
var joinArgs = [relation.localKey, relation.foreignKey] var joinArgs = [relation.localKey, relation.foreignKey]
@ -284,7 +284,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
// Just call getOne() again // Just call getOne() again
if (ctx.effectiveFields.any((f) => if (ctx.effectiveFields.any((f) =>
isSpecialId(f) || isSpecialId(ctx, f) ||
(ctx.columns[f.name]?.indexType == (ctx.columns[f.name]?.indexType ==
IndexType.primaryKey))) { IndexType.primaryKey))) {
b.addExpression(refer('where') b.addExpression(refer('where')
@ -370,7 +370,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
'but ${ctx.buildContext.clazz.name} has a @HasMany() relation that expects such a field.'; 'but ${ctx.buildContext.clazz.name} has a @HasMany() relation that expects such a field.';
}); });
var queryValue = (isSpecialId(localField)) var queryValue = (isSpecialId(ctx, localField))
? 'int.parse(model.id)' ? 'int.parse(model.id)'
: 'model.${localField.name}'; : 'model.${localField.name}';
var cascadeText = var cascadeText =
@ -445,10 +445,12 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
}); });
} }
bool isSpecialId(FieldElement field) { bool isSpecialId(OrmBuildContext ctx, FieldElement field) {
return field is ShimFieldImpl && return field is ShimFieldImpl &&
field is! RelationFieldImpl && field is! RelationFieldImpl &&
(field.name == 'id' && autoIdAndDateFields); (field.name == 'id' &&
const TypeChecker.fromRuntime(Model)
.isAssignableFromType(ctx.buildContext.clazz.type));
} }
Class buildWhereClass(OrmBuildContext ctx) { Class buildWhereClass(OrmBuildContext ctx) {
@ -475,23 +477,31 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
// Add builders for each field // Add builders for each field
for (var field in ctx.effectiveFields) { for (var field in ctx.effectiveFields) {
var name = field.name; var name = field.name;
DartType type;
Reference builderType; Reference builderType;
if (const TypeChecker.fromRuntime(int).isExactlyType(field.type) || try {
const TypeChecker.fromRuntime(double).isExactlyType(field.type) || type = ctx.buildContext.resolveSerializedFieldType(field.name);
isSpecialId(field)) { } on StateError {
type = field.type;
}
if (const TypeChecker.fromRuntime(int).isExactlyType(type) ||
const TypeChecker.fromRuntime(double).isExactlyType(type) ||
isSpecialId(ctx, field)) {
builderType = new TypeReference((b) => b builderType = new TypeReference((b) => b
..symbol = 'NumericSqlExpressionBuilder' ..symbol = 'NumericSqlExpressionBuilder'
..types.add(refer(isSpecialId(field) ? 'int' : field.type.name))); ..types.add(refer(isSpecialId(ctx, field) ? 'int' : type.name)));
} else if (const TypeChecker.fromRuntime(String) } else if (const TypeChecker.fromRuntime(String).isExactlyType(type)) {
.isExactlyType(field.type)) {
builderType = refer('StringSqlExpressionBuilder'); builderType = refer('StringSqlExpressionBuilder');
} else if (const TypeChecker.fromRuntime(bool) } else if (const TypeChecker.fromRuntime(bool).isExactlyType(type)) {
.isExactlyType(field.type)) {
builderType = refer('BooleanSqlExpressionBuilder'); builderType = refer('BooleanSqlExpressionBuilder');
} else if (const TypeChecker.fromRuntime(DateTime) } else if (const TypeChecker.fromRuntime(DateTime)
.isExactlyType(field.type)) { .isExactlyType(type)) {
builderType = refer('DateTimeSqlExpressionBuilder'); builderType = refer('DateTimeSqlExpressionBuilder');
} else if (const TypeChecker.fromRuntime(Map)
.isAssignableFromType(type)) {
builderType = refer('MapSqlExpressionBuilder');
} else if (ctx.relations.containsKey(field.name)) { } else if (ctx.relations.containsKey(field.name)) {
var relation = ctx.relations[field.name]; var relation = ctx.relations[field.name];
if (relation.type != RelationshipType.belongsTo) if (relation.type != RelationshipType.belongsTo)
@ -545,7 +555,7 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
// Each field generates a getter for setter // Each field generates a getter for setter
for (var field in ctx.effectiveFields) { for (var field in ctx.effectiveFields) {
var name = ctx.buildContext.resolveFieldName(field.name); var name = ctx.buildContext.resolveFieldName(field.name);
var type = isSpecialId(field) var type = isSpecialId(ctx, field)
? refer('int') ? refer('int')
: convertTypeReference(field.type); : convertTypeReference(field.type);
@ -584,7 +594,8 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
var args = <String, Expression>{}; var args = <String, Expression>{};
for (var field in ctx.effectiveFields) { for (var field in ctx.effectiveFields) {
if (isSpecialId(field) || field is RelationFieldImpl) continue; if (isSpecialId(ctx, field) || field is RelationFieldImpl)
continue;
args[ctx.buildContext.resolveFieldName(field.name)] = args[ctx.buildContext.resolveFieldName(field.name)] =
refer('model').property(field.name); refer('model').property(field.name);
} }

View file

@ -1,7 +1,6 @@
/// Tests for @hasOne... /// Tests for @hasOne...
library angel_orm_generator.test.has_one_test; library angel_orm_generator.test.has_one_test;
import 'package:angel_orm/angel_orm.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'models/foot.dart'; import 'models/foot.dart';
import 'models/leg.dart'; import 'models/leg.dart';

View file

@ -10,6 +10,6 @@ part 'author.g.dart';
@orm @orm
abstract class _Author extends Model { abstract class _Author extends Model {
@Column(length: 255, indexType: IndexType.unique) @Column(length: 255, indexType: IndexType.unique)
@DefaultValue('Tobe Osakwe') @SerializableField(defaultValue: 'Tobe Osakwe')
String get name; String get name;
} }

View file

@ -0,0 +1,12 @@
import 'package:angel_migration/angel_migration.dart';
import 'package:angel_model/angel_model.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize/angel_serialize.dart';
import 'car.dart';
// part 'has_car.g.dart';
@orm
@serializable
abstract class _PackageJson extends Model {
Car get car;
}

View file

@ -0,0 +1,12 @@
import 'package:angel_migration/angel_migration.dart';
import 'package:angel_model/angel_model.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize/angel_serialize.dart';
import 'package:collection/collection.dart';
part 'has_map.g.dart';
@orm
@serializable
abstract class _HasMap {
Map get value;
}

View file

@ -0,0 +1,148 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'has_map.dart';
// **************************************************************************
// MigrationGenerator
// **************************************************************************
class HasMapMigration extends Migration {
@override
up(Schema schema) {
schema.create('has_maps', (table) {
table.declare('value', new ColumnType('jsonb'));
});
}
@override
down(Schema schema) {
schema.drop('has_maps');
}
}
// **************************************************************************
// OrmGenerator
// **************************************************************************
class HasMapQuery extends Query<HasMap, HasMapQueryWhere> {
HasMapQuery() {
_where = new HasMapQueryWhere(this);
}
@override
final HasMapQueryValues values = new HasMapQueryValues();
HasMapQueryWhere _where;
@override
get tableName {
return 'has_maps';
}
@override
get fields {
return const ['value'];
}
@override
HasMapQueryWhere get where {
return _where;
}
@override
HasMapQueryWhere newWhereClause() {
return new HasMapQueryWhere(this);
}
static HasMap parseRow(List row) {
if (row.every((x) => x == null)) return null;
var model = new HasMap(value: (row[0] as Map<dynamic, dynamic>));
return model;
}
@override
deserialize(List row) {
return parseRow(row);
}
}
class HasMapQueryWhere extends QueryWhere {
HasMapQueryWhere(HasMapQuery query)
: value = new MapSqlExpressionBuilder(query, 'value');
final MapSqlExpressionBuilder value;
@override
get expressionBuilders {
return [value];
}
}
class HasMapQueryValues extends MapQueryValues {
Map<dynamic, dynamic> get value {
return (values['value'] as Map<dynamic, dynamic>);
}
set value(Map<dynamic, dynamic> value) => values['value'] = value;
void copyFrom(HasMap model) {
values.addAll({'value': model.value});
}
}
// **************************************************************************
// JsonModelGenerator
// **************************************************************************
@generatedSerializable
class HasMap implements _HasMap {
const HasMap({Map<dynamic, dynamic> this.value});
@override
final Map<dynamic, dynamic> value;
HasMap copyWith({Map<dynamic, dynamic> value}) {
return new HasMap(value: value ?? this.value);
}
bool operator ==(other) {
return other is _HasMap &&
const MapEquality<dynamic, dynamic>(
keys: const DefaultEquality(), values: const DefaultEquality())
.equals(other.value, value);
}
@override
int get hashCode {
return hashObjects([value]);
}
Map<String, dynamic> toJson() {
return HasMapSerializer.toMap(this);
}
}
// **************************************************************************
// SerializerGenerator
// **************************************************************************
abstract class HasMapSerializer {
static HasMap fromMap(Map map) {
return new HasMap(
value: map['value'] is Map
? (map['value'] as Map).cast<dynamic, dynamic>()
: null);
}
static Map<String, dynamic> toMap(_HasMap model) {
if (model == null) {
return null;
}
return {'value': model.value};
}
}
abstract class HasMapFields {
static const List<String> allFields = const <String>[value];
static const String value = 'value';
}