Testing for not-model/no default id
This commit is contained in:
parent
ee7a6d5f04
commit
832601c8c5
28 changed files with 1649 additions and 113 deletions
|
@ -1,5 +1,6 @@
|
||||||
# 2.0.0-dev.24
|
# 2.0.0-dev.24
|
||||||
* Fix a bug that caused syntax errors on `ORDER BY`.
|
* Fix a bug that caused syntax errors on `ORDER BY`.
|
||||||
|
* Add `pattern` to `like` on string builder. `sanitize` is optional.
|
||||||
|
|
||||||
# 2.0.0-dev.23
|
# 2.0.0-dev.23
|
||||||
* Add `@ManyToMany` annotation, which builds many-to-many relations.
|
* Add `@ManyToMany` annotation, which builds many-to-many relations.
|
||||||
|
|
|
@ -239,17 +239,23 @@ class StringSqlExpressionBuilder extends SqlExpressionBuilder<String> {
|
||||||
|
|
||||||
/// Builds a `LIKE` predicate.
|
/// Builds a `LIKE` predicate.
|
||||||
///
|
///
|
||||||
/// To prevent injections, the [pattern] is called with a name that
|
/// To prevent injections, an optional [sanitizer] is called with a name that
|
||||||
/// will be escaped by the underlying [QueryExecutor].
|
/// will be escaped by the underlying [QueryExecutor]. Use this if the [pattern]
|
||||||
|
/// is not constant, and/or involves user input.
|
||||||
|
///
|
||||||
|
/// Otherwise, you can omit [sanitizer].
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
|
/// carNameBuilder.like('%Mazda%');
|
||||||
/// carNameBuilder.like((name) => 'Mazda %$name%');
|
/// carNameBuilder.like((name) => 'Mazda %$name%');
|
||||||
/// ```
|
/// ```
|
||||||
void like(String Function(String) pattern) {
|
void like(String pattern, {String Function(String) sanitize}) {
|
||||||
_raw = 'LIKE \'' + pattern('@$substitution') + '\'';
|
sanitize ??= (s) => pattern;
|
||||||
query.substitutionValues[substitution] = _value;
|
_raw = 'LIKE \'' + sanitize('@$substitution') + '\'';
|
||||||
|
query.substitutionValues[substitution] = pattern;
|
||||||
_hasValue = true;
|
_hasValue = true;
|
||||||
|
_value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void isBetween(String lower, String upper) {
|
void isBetween(String lower, String upper) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ class Relationship {
|
||||||
|
|
||||||
class HasMany extends Relationship {
|
class HasMany extends Relationship {
|
||||||
const HasMany(
|
const HasMany(
|
||||||
{String localKey = 'id',
|
{String localKey,
|
||||||
String foreignKey,
|
String foreignKey,
|
||||||
String foreignTable,
|
String foreignTable,
|
||||||
bool cascadeOnDelete = false})
|
bool cascadeOnDelete = false})
|
||||||
|
@ -36,7 +36,7 @@ const HasMany hasMany = const HasMany();
|
||||||
|
|
||||||
class HasOne extends Relationship {
|
class HasOne extends Relationship {
|
||||||
const HasOne(
|
const HasOne(
|
||||||
{String localKey = 'id',
|
{String localKey,
|
||||||
String foreignKey,
|
String foreignKey,
|
||||||
String foreignTable,
|
String foreignTable,
|
||||||
bool cascadeOnDelete = false})
|
bool cascadeOnDelete = false})
|
||||||
|
@ -63,7 +63,7 @@ class ManyToMany extends Relationship {
|
||||||
final Type through;
|
final Type through;
|
||||||
|
|
||||||
const ManyToMany(this.through,
|
const ManyToMany(this.through,
|
||||||
{String localKey = 'id',
|
{String localKey,
|
||||||
String foreignKey,
|
String foreignKey,
|
||||||
String foreignTable,
|
String foreignTable,
|
||||||
bool cascadeOnDelete = false})
|
bool cascadeOnDelete = false})
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
# 2.0.0-dev.7
|
||||||
|
* Handle `@ManyToMany`.
|
||||||
|
* Handle cases where the class is not a `Model`.
|
||||||
|
* Stop assuming things have `id`, etc.
|
||||||
|
* Resolve a bug where the `indexType` of `@Column` annotations. would not be found.
|
||||||
|
|
||||||
# 2.0.0-dev.6
|
# 2.0.0-dev.6
|
||||||
* Fix bug where an extra field would be inserted into joins and botch the result.
|
* Fix bug where an extra field would be inserted into joins and botch the result.
|
||||||
* Narrow analyzer dependency.
|
* Narrow analyzer dependency.
|
||||||
|
|
|
@ -28,6 +28,7 @@ targets:
|
||||||
- test/models/fruit.dart
|
- test/models/fruit.dart
|
||||||
- test/models/has_map.dart
|
- test/models/has_map.dart
|
||||||
- test/models/role.dart
|
- test/models/role.dart
|
||||||
|
- test/models/unorthodox.dart
|
||||||
$default:
|
$default:
|
||||||
dependencies:
|
dependencies:
|
||||||
- angel_serialize_generator
|
- angel_serialize_generator
|
||||||
|
|
|
@ -19,6 +19,39 @@ import 'readers.dart';
|
||||||
bool isHasRelation(Relationship r) =>
|
bool isHasRelation(Relationship r) =>
|
||||||
r.type == RelationshipType.hasOne || r.type == RelationshipType.hasMany;
|
r.type == RelationshipType.hasOne || r.type == RelationshipType.hasMany;
|
||||||
|
|
||||||
|
bool isSpecialId(OrmBuildContext ctx, FieldElement field) {
|
||||||
|
return field is ShimFieldImpl &&
|
||||||
|
field is! RelationFieldImpl &&
|
||||||
|
(field.name == 'id' &&
|
||||||
|
const TypeChecker.fromRuntime(Model)
|
||||||
|
.isAssignableFromType(ctx.buildContext.clazz.type));
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldElement findPrimaryFieldInList(
|
||||||
|
OrmBuildContext ctx, Iterable<FieldElement> fields) {
|
||||||
|
for (var field_ in fields) {
|
||||||
|
var field = field_ is RelationFieldImpl ? field_.originalField : field_;
|
||||||
|
var element = field.getter ?? field;
|
||||||
|
// print(
|
||||||
|
// 'Searching in ${ctx.buildContext.originalClassName}=>${field?.name} (${field.runtimeType})');
|
||||||
|
// Check for column annotation...
|
||||||
|
var columnAnnotation = columnTypeChecker.firstAnnotationOf(element);
|
||||||
|
|
||||||
|
if (columnAnnotation != null) {
|
||||||
|
var column = reviveColumn(new ConstantReader(columnAnnotation));
|
||||||
|
// print(
|
||||||
|
// ' * Found column on ${field.name} with indexType = ${column.indexType}');
|
||||||
|
if (column.indexType == IndexType.primaryKey) return field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var specialId =
|
||||||
|
fields.firstWhere((f) => isSpecialId(ctx, f), orElse: () => null);
|
||||||
|
// print(
|
||||||
|
// 'Special ID on ${ctx.buildContext.originalClassName} => ${specialId?.name}');
|
||||||
|
return specialId;
|
||||||
|
}
|
||||||
|
|
||||||
final Map<String, OrmBuildContext> _cache = {};
|
final Map<String, OrmBuildContext> _cache = {};
|
||||||
|
|
||||||
Future<OrmBuildContext> buildOrmContext(
|
Future<OrmBuildContext> buildOrmContext(
|
||||||
|
@ -59,19 +92,18 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
for (var field in buildCtx.fields) {
|
for (var field in buildCtx.fields) {
|
||||||
// Check for column annotation...
|
// Check for column annotation...
|
||||||
Column column;
|
Column column;
|
||||||
var columnAnnotation = columnTypeChecker.firstAnnotationOf(field);
|
var element = field.getter ?? field;
|
||||||
|
var columnAnnotation = columnTypeChecker.firstAnnotationOf(element);
|
||||||
|
|
||||||
if (columnAnnotation != null) {
|
if (columnAnnotation != null) {
|
||||||
column = reviveColumn(new ConstantReader(columnAnnotation));
|
column = reviveColumn(new ConstantReader(columnAnnotation));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (column == null &&
|
if (column == null && isSpecialId(ctx, field)) {
|
||||||
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, indexType: IndexType.primaryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (column == null) {
|
if (column == null) {
|
||||||
|
@ -168,12 +200,30 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
|
|
||||||
// Fill in missing keys
|
// Fill in missing keys
|
||||||
var rcc = new ReCase(field.name);
|
var rcc = new ReCase(field.name);
|
||||||
|
|
||||||
|
String keyName(OrmBuildContext ctx, String missing) {
|
||||||
|
var _keyName =
|
||||||
|
findPrimaryFieldInList(ctx, ctx.buildContext.fields)?.name;
|
||||||
|
// print(
|
||||||
|
// 'Keyname for ${buildCtx.originalClassName}.${field.name} maybe = $_keyName??');
|
||||||
|
if (_keyName == null) {
|
||||||
|
throw '${ctx.buildContext.originalClassName} has no defined primary key, '
|
||||||
|
'so the relation on field ${buildCtx.originalClassName}.${field.name} must define a $missing.';
|
||||||
|
} else {
|
||||||
|
return _keyName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (type == RelationshipType.hasOne || type == RelationshipType.hasMany) {
|
if (type == RelationshipType.hasOne || type == RelationshipType.hasMany) {
|
||||||
localKey ??= 'id';
|
localKey ??=
|
||||||
foreignKey ??= '${rc.snakeCase}_id';
|
ctx.buildContext.resolveFieldName(keyName(ctx, 'local key'));
|
||||||
|
// print(
|
||||||
|
// 'Local key on ${buildCtx.originalClassName}.${field.name} defaulted to $localKey');
|
||||||
|
foreignKey ??= '${rc.snakeCase}_$localKey';
|
||||||
} else if (type == RelationshipType.belongsTo) {
|
} else if (type == RelationshipType.belongsTo) {
|
||||||
localKey ??= '${rcc.snakeCase}_id';
|
foreignKey ??=
|
||||||
foreignKey ??= 'id';
|
ctx.buildContext.resolveFieldName(keyName(foreign, 'foreign key'));
|
||||||
|
localKey ??= '${rcc.snakeCase}_$foreignKey';
|
||||||
}
|
}
|
||||||
|
|
||||||
var relation = new RelationshipReader(
|
var relation = new RelationshipReader(
|
||||||
|
@ -187,19 +237,21 @@ Future<OrmBuildContext> buildOrmContext(
|
||||||
throughContext: throughContext,
|
throughContext: throughContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// print('Relation on ${buildCtx.originalClassName}.${field.name} => '
|
||||||
|
// 'foreignKey=$foreignKey, localKey=$localKey');
|
||||||
|
|
||||||
if (relation.type == RelationshipType.belongsTo) {
|
if (relation.type == RelationshipType.belongsTo) {
|
||||||
var name = new ReCase(relation.localKey).camelCase;
|
var name = new ReCase(relation.localKey).camelCase;
|
||||||
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)) {
|
||||||
// TODO: Consequences of allowing ID to be a relation? (should be none)
|
var foreignField = relation.findForeignField(ctx);
|
||||||
// if (field.name != 'id' ||
|
var foreign = relation.throughContext ?? relation.foreign;
|
||||||
// !const TypeChecker.fromRuntime(Model)
|
var type = foreignField.type;
|
||||||
// .isAssignableFromType(ctx.buildContext.clazz.type)) {
|
if (isSpecialId(foreign, foreignField))
|
||||||
var rf = new RelationFieldImpl(name,
|
type = field.type.element.context.typeProvider.intType;
|
||||||
field.type.element.context.typeProvider.intType, field.name);
|
var rf = new RelationFieldImpl(name, relation, type, field);
|
||||||
ctx.effectiveFields.add(rf);
|
ctx.effectiveFields.add(rf);
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,17 +291,16 @@ ColumnType inferColumnType(DartType type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Column reviveColumn(ConstantReader cr) {
|
Column reviveColumn(ConstantReader cr) {
|
||||||
var args = cr.revive().namedArguments;
|
|
||||||
IndexType indexType = IndexType.none;
|
|
||||||
ColumnType columnType;
|
ColumnType columnType;
|
||||||
|
|
||||||
if (args.containsKey('index')) {
|
var columnObj =
|
||||||
indexType =
|
cr.peek('type')?.objectValue?.getField('name')?.toStringValue();
|
||||||
IndexType.values[args['indexType'].getField('index').toIntValue()];
|
var indexType = IndexType.values[
|
||||||
}
|
cr.peek('indexType')?.objectValue?.getField('index')?.toIntValue() ??
|
||||||
|
IndexType.none.index];
|
||||||
|
|
||||||
if (args.containsKey('type')) {
|
if (columnObj != null) {
|
||||||
columnType = new _ColumnType(args['type'].getField('name').toStringValue());
|
columnType = new _ColumnType(columnObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Column(
|
return new Column(
|
||||||
|
@ -283,9 +334,15 @@ class _ColumnType implements ColumnType {
|
||||||
}
|
}
|
||||||
|
|
||||||
class RelationFieldImpl extends ShimFieldImpl {
|
class RelationFieldImpl extends ShimFieldImpl {
|
||||||
final String originalFieldName;
|
final FieldElement originalField;
|
||||||
RelationFieldImpl(String name, DartType type, this.originalFieldName)
|
final RelationshipReader relationship;
|
||||||
|
RelationFieldImpl(
|
||||||
|
String name, this.relationship, DartType type, this.originalField)
|
||||||
: super(name, type);
|
: super(name, type);
|
||||||
|
|
||||||
|
String get originalFieldName => originalField.name;
|
||||||
|
|
||||||
|
PropertyAccessorElement get getter => originalField.getter;
|
||||||
}
|
}
|
||||||
|
|
||||||
InterfaceType firstModelAncestor(DartType type) {
|
InterfaceType firstModelAncestor(DartType type) {
|
||||||
|
|
|
@ -8,7 +8,6 @@ import 'package:angel_serialize_generator/build_context.dart';
|
||||||
import 'package:build/build.dart';
|
import 'package:build/build.dart';
|
||||||
import 'package:code_builder/code_builder.dart' hide LibraryBuilder;
|
import 'package:code_builder/code_builder.dart' hide LibraryBuilder;
|
||||||
import 'package:source_gen/source_gen.dart';
|
import 'package:source_gen/source_gen.dart';
|
||||||
|
|
||||||
import 'orm_build_context.dart';
|
import 'orm_build_context.dart';
|
||||||
|
|
||||||
var floatTypes = [
|
var floatTypes = [
|
||||||
|
@ -434,21 +433,8 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
var queryInstance = queryType.newInstance([]);
|
var queryInstance = queryType.newInstance([]);
|
||||||
|
|
||||||
// Next, we need to apply a cascade that sets the correct query value.
|
// Next, we need to apply a cascade that sets the correct query value.
|
||||||
var localField = ctx.effectiveFields.firstWhere(
|
var localField = relation.findLocalField(ctx);
|
||||||
(f) =>
|
var foreignField = relation.findForeignField(ctx);
|
||||||
ctx.buildContext.resolveFieldName(f.name) ==
|
|
||||||
relation.localKey, orElse: () {
|
|
||||||
throw '${ctx.buildContext.clazz.name} has no field that maps to the name "${relation.localKey}", '
|
|
||||||
'but it has a @HasMany() relation that expects such a field.';
|
|
||||||
});
|
|
||||||
|
|
||||||
var foreignField = foreign.effectiveFields.firstWhere(
|
|
||||||
(f) =>
|
|
||||||
foreign.buildContext.resolveFieldName(f.name) ==
|
|
||||||
relation.foreignKey, orElse: () {
|
|
||||||
throw '${foreign.buildContext.clazz.name} has no field that maps to the name "${relation.foreignKey}", '
|
|
||||||
'but ${ctx.buildContext.clazz.name} has a @HasMany() relation that expects such a field.';
|
|
||||||
});
|
|
||||||
|
|
||||||
var queryValue = (isSpecialId(ctx, localField))
|
var queryValue = (isSpecialId(ctx, localField))
|
||||||
? 'int.parse(model.id)'
|
? 'int.parse(model.id)'
|
||||||
|
@ -505,10 +491,17 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
|
|
||||||
var merged = merge.join(', ');
|
var merged = merge.join(', ');
|
||||||
|
|
||||||
|
var keyName =
|
||||||
|
findPrimaryFieldInList(ctx, ctx.buildContext.fields)?.name;
|
||||||
|
if (keyName == null) {
|
||||||
|
throw '${ctx.buildContext.originalClassName} has no defined primary key.\n'
|
||||||
|
'@HasMany and @ManyToMany relations require a primary key to be defined on the model.';
|
||||||
|
}
|
||||||
|
|
||||||
b.body = new Code('''
|
b.body = new Code('''
|
||||||
return super.$methodName(executor).then((result) {
|
return super.$methodName(executor).then((result) {
|
||||||
return result.fold<List<$type>>([], (out, model) {
|
return result.fold<List<$type>>([], (out, model) {
|
||||||
var idx = out.indexWhere((m) => m.id == model.id);
|
var idx = out.indexWhere((m) => m.$keyName == model.$keyName);
|
||||||
|
|
||||||
if (idx == -1) {
|
if (idx == -1) {
|
||||||
return out..add(model);
|
return out..add(model);
|
||||||
|
@ -525,14 +518,6 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSpecialId(OrmBuildContext ctx, FieldElement field) {
|
|
||||||
return field is ShimFieldImpl &&
|
|
||||||
field is! RelationFieldImpl &&
|
|
||||||
(field.name == 'id' &&
|
|
||||||
const TypeChecker.fromRuntime(Model)
|
|
||||||
.isAssignableFromType(ctx.buildContext.clazz.type));
|
|
||||||
}
|
|
||||||
|
|
||||||
Class buildWhereClass(OrmBuildContext ctx) {
|
Class buildWhereClass(OrmBuildContext ctx) {
|
||||||
return new Class((clazz) {
|
return new Class((clazz) {
|
||||||
var rc = ctx.buildContext.modelClassNameRecase;
|
var rc = ctx.buildContext.modelClassNameRecase;
|
||||||
|
@ -667,13 +652,11 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Each field generates a getter for setter
|
// Each field generates a getter and setter
|
||||||
for (var field in ctx.effectiveFields) {
|
for (var field in ctx.effectiveFields) {
|
||||||
var fType = field.type;
|
var fType = field.type;
|
||||||
var name = ctx.buildContext.resolveFieldName(field.name);
|
var name = ctx.buildContext.resolveFieldName(field.name);
|
||||||
var type = isSpecialId(ctx, field)
|
var type = convertTypeReference(field.type);
|
||||||
? refer('int')
|
|
||||||
: convertTypeReference(field.type);
|
|
||||||
|
|
||||||
clazz.methods.add(new Method((b) {
|
clazz.methods.add(new Method((b) {
|
||||||
var value = refer('values').index(literalString(name));
|
var value = refer('values').index(literalString(name));
|
||||||
|
@ -748,9 +731,15 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
|
||||||
// Add only if present
|
// Add only if present
|
||||||
var target = refer('values').index(literalString(
|
var target = refer('values').index(literalString(
|
||||||
ctx.buildContext.resolveFieldName(field.name)));
|
ctx.buildContext.resolveFieldName(field.name)));
|
||||||
var parsedId = (refer('int')
|
var foreign = field.relationship.throughContext ??
|
||||||
.property('parse')
|
field.relationship.foreign;
|
||||||
.call([prop.property('id')]));
|
var foreignField = field.relationship.findForeignField(ctx);
|
||||||
|
var parsedId = prop.property(foreignField.name);
|
||||||
|
|
||||||
|
if (isSpecialId(foreign, field)) {
|
||||||
|
parsedId = (refer('int').property('parse').call([parsedId]));
|
||||||
|
}
|
||||||
|
|
||||||
var cond = prop.notEqualTo(literalNull);
|
var cond = prop.notEqualTo(literalNull);
|
||||||
var condStr = cond.accept(new DartEmitter());
|
var condStr = cond.accept(new DartEmitter());
|
||||||
var blkStr =
|
var blkStr =
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:analyzer/dart/constant/value.dart';
|
import 'package:analyzer/dart/constant/value.dart';
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
import 'package:analyzer/dart/element/type.dart';
|
import 'package:analyzer/dart/element/type.dart';
|
||||||
import 'package:angel_orm/angel_orm.dart';
|
import 'package:angel_orm/angel_orm.dart';
|
||||||
import 'package:source_gen/source_gen.dart';
|
import 'package:source_gen/source_gen.dart';
|
||||||
|
@ -43,4 +44,23 @@ class RelationshipReader {
|
||||||
|
|
||||||
bool get isManyToMany =>
|
bool get isManyToMany =>
|
||||||
type == RelationshipType.hasMany && throughContext != null;
|
type == RelationshipType.hasMany && throughContext != null;
|
||||||
|
|
||||||
|
FieldElement findLocalField(OrmBuildContext ctx) {
|
||||||
|
return ctx.effectiveFields.firstWhere(
|
||||||
|
(f) => ctx.buildContext.resolveFieldName(f.name) == localKey,
|
||||||
|
orElse: () {
|
||||||
|
throw '${ctx.buildContext.clazz.name} has no field that maps to the name "$localKey", '
|
||||||
|
'but it has a @HasMany() relation that expects such a field.';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldElement findForeignField(OrmBuildContext ctx) {
|
||||||
|
var foreign = throughContext ?? this.foreign;
|
||||||
|
return foreign.effectiveFields.firstWhere(
|
||||||
|
(f) => foreign.buildContext.resolveFieldName(f.name) == foreignKey,
|
||||||
|
orElse: () {
|
||||||
|
throw '${foreign.buildContext.clazz.name} has no field that maps to the name "$foreignKey", '
|
||||||
|
'but ${ctx.buildContext.clazz.name} has a @HasMany() relation that expects such a field.';
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('union', () async {
|
test('union', () async {
|
||||||
var query1 = new BookQuery()..where.name.like((_) => 'Deathly%');
|
var query1 = new BookQuery()..where.name.like('Deathly%');
|
||||||
var query2 = new BookQuery()..where.authorId.equals(-1);
|
var query2 = new BookQuery()..where.authorId.equals(-1);
|
||||||
var query3 = new BookQuery()
|
var query3 = new BookQuery()
|
||||||
..where.name.isIn(['Goblet of Fire', 'Order of the Phoenix']);
|
..where.name.isIn(['Goblet of Fire', 'Order of the Phoenix']);
|
||||||
|
|
89
angel_orm_generator/test/edge_case_test.dart
Normal file
89
angel_orm_generator/test/edge_case_test.dart
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'models/unorthodox.dart';
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
PostgresExecutor executor;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
executor =
|
||||||
|
await connectToPostgres(['unorthodox', 'weird_join', 'song', 'numba']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can create object with no id', () async {
|
||||||
|
var query = UnorthodoxQuery()..values.name = 'Hey';
|
||||||
|
var model = await query.insert(executor);
|
||||||
|
expect(model, Unorthodox(name: 'Hey'));
|
||||||
|
});
|
||||||
|
|
||||||
|
group('relations on non-model', () {
|
||||||
|
Unorthodox unorthodox;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
var query = UnorthodoxQuery()..values.name = 'Hey';
|
||||||
|
unorthodox = await query.insert(executor);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('belongs to', () async {
|
||||||
|
var query = WeirdJoinQuery()..values.joinName = unorthodox.name;
|
||||||
|
var model = await query.insert(executor);
|
||||||
|
print(model.toJson());
|
||||||
|
expect(model.id, isNotNull); // Postgres should set this.
|
||||||
|
expect(model.unorthodox, unorthodox);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('layered', () {
|
||||||
|
WeirdJoin weirdJoin;
|
||||||
|
Song girlBlue;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
var wjQuery = WeirdJoinQuery()..values.joinName = unorthodox.name;
|
||||||
|
weirdJoin = await wjQuery.insert(executor);
|
||||||
|
|
||||||
|
var gbQuery = SongQuery()
|
||||||
|
..values.weirdJoinId = weirdJoin.id
|
||||||
|
..values.title = 'Girl Blue';
|
||||||
|
girlBlue = await gbQuery.insert(executor);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has one', () async {
|
||||||
|
var query = WeirdJoinQuery()..where.id.equals(weirdJoin.id);
|
||||||
|
var wj = await query.getOne(executor);
|
||||||
|
print(wj.toJson());
|
||||||
|
expect(wj.song, girlBlue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has many', () async {
|
||||||
|
var numbas = <Numba>[];
|
||||||
|
|
||||||
|
for (int i = 0; i < 15; i++) {
|
||||||
|
var query = NumbaQuery()
|
||||||
|
..values.parent = weirdJoin.id
|
||||||
|
..values.i = i;
|
||||||
|
var model = await query.insert(executor);
|
||||||
|
numbas.add(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = WeirdJoinQuery()..where.id.equals(weirdJoin.id);
|
||||||
|
var wj = await query.getOne(executor);
|
||||||
|
print(wj.toJson());
|
||||||
|
expect(wj.numbas, numbas);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('many to many', () async {
|
||||||
|
var fooQuery = FooQuery()..values.bar = 'baz';
|
||||||
|
var fooBar = await fooQuery.insert(executor).then((foo) => foo.bar);
|
||||||
|
var pivotQuery = FooPivotQuery()
|
||||||
|
..values.weirdJoinId = weirdJoin.id
|
||||||
|
..values.fooBar = fooBar;
|
||||||
|
await pivotQuery.insert(executor);
|
||||||
|
fooQuery = FooQuery()..where.bar.equals('baz');
|
||||||
|
|
||||||
|
var foo = await fooQuery.getOne(executor);
|
||||||
|
print(foo.toJson());
|
||||||
|
print(weirdJoin.toJson());
|
||||||
|
expect(foo.weirdJoins[0].id, weirdJoin.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
7
angel_orm_generator/test/migrations/numba.sql
Normal file
7
angel_orm_generator/test/migrations/numba.sql
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
CREATE TEMPORARY TABLE "numbas" (
|
||||||
|
"i" int,
|
||||||
|
"parent" int references weird_joins(id),
|
||||||
|
created_at TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP,
|
||||||
|
PRIMARY KEY(i)
|
||||||
|
);
|
8
angel_orm_generator/test/migrations/song.sql
Normal file
8
angel_orm_generator/test/migrations/song.sql
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
CREATE TEMPORARY TABLE "songs" (
|
||||||
|
"id" serial,
|
||||||
|
"weird_join_id" int references weird_joins(id),
|
||||||
|
"title" varchar(255),
|
||||||
|
created_at TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP,
|
||||||
|
PRIMARY KEY(id)
|
||||||
|
);
|
4
angel_orm_generator/test/migrations/unorthodox.sql
Normal file
4
angel_orm_generator/test/migrations/unorthodox.sql
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
CREATE TEMPORARY TABLE "unorthodoxes" (
|
||||||
|
"name" varchar(255),
|
||||||
|
PRIMARY KEY(name)
|
||||||
|
);
|
13
angel_orm_generator/test/migrations/weird_join.sql
Normal file
13
angel_orm_generator/test/migrations/weird_join.sql
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
CREATE TEMPORARY TABLE "weird_joins" (
|
||||||
|
"id" serial,
|
||||||
|
"join_name" varchar(255) references unorthodoxes(name),
|
||||||
|
PRIMARY KEY(id)
|
||||||
|
);
|
||||||
|
CREATE TEMPORARY TABLE "foos" (
|
||||||
|
"bar" varchar(255),
|
||||||
|
PRIMARY KEY(bar)
|
||||||
|
);
|
||||||
|
CREATE TEMPORARY TABLE "foo_pivots" (
|
||||||
|
"weird_join_id" int references weird_joins(id),
|
||||||
|
"foo_bar" varchar(255) references foos(bar)
|
||||||
|
);
|
|
@ -11,7 +11,9 @@ class AuthorMigration extends Migration {
|
||||||
up(Schema schema) {
|
up(Schema schema) {
|
||||||
schema.create('authors', (table) {
|
schema.create('authors', (table) {
|
||||||
table.serial('id')..primaryKey();
|
table.serial('id')..primaryKey();
|
||||||
table.varChar('name')..defaultsTo('Tobe Osakwe');
|
table.varChar('name', length: 255)
|
||||||
|
..defaultsTo('Tobe Osakwe')
|
||||||
|
..unique();
|
||||||
table.timeStamp('created_at');
|
table.timeStamp('created_at');
|
||||||
table.timeStamp('updated_at');
|
table.timeStamp('updated_at');
|
||||||
});
|
});
|
||||||
|
@ -107,11 +109,11 @@ class AuthorQueryValues extends MapQueryValues {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int get id {
|
String get id {
|
||||||
return (values['id'] as int);
|
return (values['id'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
set id(int value) => values['id'] = value;
|
set id(String value) => values['id'] = value;
|
||||||
String get name {
|
String get name {
|
||||||
return (values['name'] as String);
|
return (values['name'] as String);
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,11 +137,11 @@ class BookQueryValues extends MapQueryValues {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int get id {
|
String get id {
|
||||||
return (values['id'] as int);
|
return (values['id'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
set id(int value) => values['id'] = value;
|
set id(String value) => values['id'] = value;
|
||||||
int get authorId {
|
int get authorId {
|
||||||
return (values['author_id'] as int);
|
return (values['author_id'] as int);
|
||||||
}
|
}
|
||||||
|
@ -172,10 +172,10 @@ class BookQueryValues extends MapQueryValues {
|
||||||
createdAt = model.createdAt;
|
createdAt = model.createdAt;
|
||||||
updatedAt = model.updatedAt;
|
updatedAt = model.updatedAt;
|
||||||
if (model.author != null) {
|
if (model.author != null) {
|
||||||
values['author_id'] = int.parse(model.author.id);
|
values['author_id'] = model.author.id;
|
||||||
}
|
}
|
||||||
if (model.partnerAuthor != null) {
|
if (model.partnerAuthor != null) {
|
||||||
values['partner_author_id'] = int.parse(model.partnerAuthor.id);
|
values['partner_author_id'] = model.partnerAuthor.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,11 +138,11 @@ class CarQueryValues extends MapQueryValues {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int get id {
|
String get id {
|
||||||
return (values['id'] as int);
|
return (values['id'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
set id(int value) => values['id'] = value;
|
set id(String value) => values['id'] = value;
|
||||||
String get make {
|
String get make {
|
||||||
return (values['make'] as String);
|
return (values['make'] as String);
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,11 +102,11 @@ class CustomerQueryValues extends MapQueryValues {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int get id {
|
String get id {
|
||||||
return (values['id'] as int);
|
return (values['id'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
set id(int value) => values['id'] = value;
|
set id(String value) => values['id'] = value;
|
||||||
DateTime get createdAt {
|
DateTime get createdAt {
|
||||||
return (values['created_at'] as DateTime);
|
return (values['created_at'] as DateTime);
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,11 +112,11 @@ class FootQueryValues extends MapQueryValues {
|
||||||
return {'n_toes': 'decimal'};
|
return {'n_toes': 'decimal'};
|
||||||
}
|
}
|
||||||
|
|
||||||
int get id {
|
String get id {
|
||||||
return (values['id'] as int);
|
return (values['id'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
set id(int value) => values['id'] = value;
|
set id(String value) => values['id'] = value;
|
||||||
int get legId {
|
int get legId {
|
||||||
return (values['leg_id'] as int);
|
return (values['leg_id'] as int);
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,11 +112,11 @@ class FruitQueryValues extends MapQueryValues {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int get id {
|
String get id {
|
||||||
return (values['id'] as int);
|
return (values['id'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
set id(int value) => values['id'] = value;
|
set id(String value) => values['id'] = value;
|
||||||
int get treeId {
|
int get treeId {
|
||||||
return (values['tree_id'] as int);
|
return (values['tree_id'] as int);
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,11 +107,11 @@ class HasCarQueryValues extends MapQueryValues {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int get id {
|
String get id {
|
||||||
return (values['id'] as int);
|
return (values['id'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
set id(int value) => values['id'] = value;
|
set id(String value) => values['id'] = value;
|
||||||
CarType get type {
|
CarType get type {
|
||||||
return CarType.values[(values['type'] as int)];
|
return CarType.values[(values['type'] as int)];
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,11 +119,11 @@ class LegQueryValues extends MapQueryValues {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int get id {
|
String get id {
|
||||||
return (values['id'] as int);
|
return (values['id'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
set id(int value) => values['id'] = value;
|
set id(String value) => values['id'] = value;
|
||||||
String get name {
|
String get name {
|
||||||
return (values['name'] as String);
|
return (values['name'] as String);
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,11 +144,11 @@ class OrderQueryValues extends MapQueryValues {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int get id {
|
String get id {
|
||||||
return (values['id'] as int);
|
return (values['id'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
set id(int value) => values['id'] = value;
|
set id(String value) => values['id'] = value;
|
||||||
int get customerId {
|
int get customerId {
|
||||||
return (values['customer_id'] as int);
|
return (values['customer_id'] as int);
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ class OrderQueryValues extends MapQueryValues {
|
||||||
createdAt = model.createdAt;
|
createdAt = model.createdAt;
|
||||||
updatedAt = model.updatedAt;
|
updatedAt = model.updatedAt;
|
||||||
if (model.customer != null) {
|
if (model.customer != null) {
|
||||||
values['customer_id'] = int.parse(model.customer.id);
|
values['customer_id'] = model.customer.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ class TreeMigration extends Migration {
|
||||||
up(Schema schema) {
|
up(Schema schema) {
|
||||||
schema.create('trees', (table) {
|
schema.create('trees', (table) {
|
||||||
table.serial('id')..primaryKey();
|
table.serial('id')..primaryKey();
|
||||||
table.declare('rings', ColumnType('smallint'));
|
table.integer('rings');
|
||||||
table.timeStamp('created_at');
|
table.timeStamp('created_at');
|
||||||
table.timeStamp('updated_at');
|
table.timeStamp('updated_at');
|
||||||
});
|
});
|
||||||
|
@ -179,11 +179,11 @@ class TreeQueryValues extends MapQueryValues {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int get id {
|
String get id {
|
||||||
return (values['id'] as int);
|
return (values['id'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
set id(int value) => values['id'] = value;
|
set id(String value) => values['id'] = value;
|
||||||
int get rings {
|
int get rings {
|
||||||
return (values['rings'] as int);
|
return (values['rings'] as int);
|
||||||
}
|
}
|
||||||
|
|
70
angel_orm_generator/test/models/unorthodox.dart
Normal file
70
angel_orm_generator/test/models/unorthodox.dart
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
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';
|
||||||
|
part 'unorthodox.g.dart';
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
@orm
|
||||||
|
abstract class _Unorthodox {
|
||||||
|
@Column(indexType: IndexType.primaryKey)
|
||||||
|
String get name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
@orm
|
||||||
|
abstract class _WeirdJoin {
|
||||||
|
@primaryKey
|
||||||
|
int get id;
|
||||||
|
|
||||||
|
@BelongsTo(localKey: 'join_name', foreignKey: 'name')
|
||||||
|
_Unorthodox get unorthodox;
|
||||||
|
|
||||||
|
@hasOne
|
||||||
|
_Song get song;
|
||||||
|
|
||||||
|
@HasMany(foreignKey: 'parent')
|
||||||
|
List<_Numba> get numbas;
|
||||||
|
|
||||||
|
@ManyToMany(_FooPivot)
|
||||||
|
List<_Foo> get foos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
@orm
|
||||||
|
abstract class _Song extends Model {
|
||||||
|
int get weirdJoinId;
|
||||||
|
|
||||||
|
String get title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
@orm
|
||||||
|
class _Numba implements Comparable<_Numba> {
|
||||||
|
@primaryKey
|
||||||
|
int i;
|
||||||
|
|
||||||
|
int parent;
|
||||||
|
|
||||||
|
int compareTo(_Numba other) => i.compareTo(other.i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
@orm
|
||||||
|
abstract class _Foo {
|
||||||
|
@primaryKey
|
||||||
|
String get bar;
|
||||||
|
|
||||||
|
@ManyToMany(_FooPivot)
|
||||||
|
List<_WeirdJoin> get weirdJoins;
|
||||||
|
}
|
||||||
|
|
||||||
|
@serializable
|
||||||
|
@orm
|
||||||
|
abstract class _FooPivot {
|
||||||
|
@belongsTo
|
||||||
|
_WeirdJoin get weirdJoin;
|
||||||
|
|
||||||
|
@belongsTo
|
||||||
|
_Foo get foo;
|
||||||
|
}
|
1263
angel_orm_generator/test/models/unorthodox.g.dart
Normal file
1263
angel_orm_generator/test/models/unorthodox.g.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -228,11 +228,11 @@ class UserQueryValues extends MapQueryValues {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int get id {
|
String get id {
|
||||||
return (values['id'] as int);
|
return (values['id'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
set id(int value) => values['id'] = value;
|
set id(String value) => values['id'] = value;
|
||||||
String get username {
|
String get username {
|
||||||
return (values['username'] as String);
|
return (values['username'] as String);
|
||||||
}
|
}
|
||||||
|
@ -368,10 +368,10 @@ class RoleUserQueryValues extends MapQueryValues {
|
||||||
set userId(int value) => values['user_id'] = value;
|
set userId(int value) => values['user_id'] = value;
|
||||||
void copyFrom(RoleUser model) {
|
void copyFrom(RoleUser model) {
|
||||||
if (model.role != null) {
|
if (model.role != null) {
|
||||||
values['role_id'] = int.parse(model.role.id);
|
values['role_id'] = model.role.id;
|
||||||
}
|
}
|
||||||
if (model.user != null) {
|
if (model.user != null) {
|
||||||
values['user_id'] = int.parse(model.user.id);
|
values['user_id'] = model.user.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -535,11 +535,11 @@ class RoleQueryValues extends MapQueryValues {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int get id {
|
String get id {
|
||||||
return (values['id'] as int);
|
return (values['id'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
set id(int value) => values['id'] = value;
|
set id(String value) => values['id'] = value;
|
||||||
String get name {
|
String get name {
|
||||||
return (values['name'] as String);
|
return (values['name'] as String);
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('union', () async {
|
test('union', () async {
|
||||||
var query1 = new CarQuery()..where.make.like((_) => '%Fer%');
|
var query1 = new CarQuery()..where.make.like('%Fer%');
|
||||||
var query2 = new CarQuery()..where.familyFriendly.isTrue;
|
var query2 = new CarQuery()..where.familyFriendly.isTrue;
|
||||||
var query3 = new CarQuery()..where.description.equals('Submarine');
|
var query3 = new CarQuery()..where.description.equals('Submarine');
|
||||||
var union = query1.union(query2).unionAll(query3);
|
var union = query1.union(query2).unionAll(query3);
|
||||||
|
@ -89,7 +89,7 @@ main() {
|
||||||
|
|
||||||
test('or clause', () async {
|
test('or clause', () async {
|
||||||
var query = new CarQuery()
|
var query = new CarQuery()
|
||||||
..where.make.like((_) => 'Fer%')
|
..where.make.like('Fer%')
|
||||||
..orWhere((where) => where
|
..orWhere((where) => where
|
||||||
..familyFriendly.isTrue
|
..familyFriendly.isTrue
|
||||||
..make.equals('Honda'));
|
..make.equals('Honda'));
|
||||||
|
|
Loading…
Reference in a new issue