This commit is contained in:
Tobe O 2019-01-24 12:20:34 -05:00
parent 599104f353
commit 50c4b394c7
24 changed files with 377 additions and 229 deletions

View file

@ -1,3 +1,6 @@
# 2.0.0-dev.18
* Add `ListSqlExpressionBuilder` (still in development).
# 2.0.0-dev.17 # 2.0.0-dev.17
* Add `EnumSqlExpressionBuilder`. * Add `EnumSqlExpressionBuilder`.

View file

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:charcode/ascii.dart'; import 'package:charcode/ascii.dart';
import 'package:intl/intl.dart' show DateFormat; import 'package:intl/intl.dart' show DateFormat;
import 'package:string_scanner/string_scanner.dart'; import 'package:string_scanner/string_scanner.dart';
@ -47,14 +49,6 @@ abstract class SqlExpressionBuilder<T> {
bool get hasValue; bool get hasValue;
String compile(); 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> class NumericSqlExpressionBuilder<T extends num>
@ -116,25 +110,21 @@ class NumericSqlExpressionBuilder<T extends num>
_change('!=', value); _change('!=', value);
} }
@override
void isBetween(T lower, T upper) { void isBetween(T lower, T upper) {
_raw = 'BETWEEN $lower AND $upper'; _raw = 'BETWEEN $lower AND $upper';
_hasValue = true; _hasValue = true;
} }
@override
void isNotBetween(T lower, T upper) { void isNotBetween(T lower, T upper) {
_raw = 'NOT BETWEEN $lower AND $upper'; _raw = 'NOT BETWEEN $lower AND $upper';
_hasValue = true; _hasValue = true;
} }
@override
void isIn(Iterable<T> values) { void isIn(Iterable<T> values) {
_raw = 'IN (' + values.join(', ') + ')'; _raw = 'IN (' + values.join(', ') + ')';
_hasValue = true; _hasValue = true;
} }
@override
void isNotIn(Iterable<T> values) { void isNotIn(Iterable<T> values) {
_raw = 'NOT IN (' + values.join(', ') + ')'; _raw = 'NOT IN (' + values.join(', ') + ')';
_hasValue = true; _hasValue = true;
@ -189,19 +179,15 @@ class EnumSqlExpressionBuilder<T> extends SqlExpressionBuilder<T> {
_change('!=', value); _change('!=', value);
} }
@override
void isBetween(T lower, T upper) => throw _unsupported(); void isBetween(T lower, T upper) => throw _unsupported();
@override
void isNotBetween(T lower, T upper) => throw _unsupported(); void isNotBetween(T lower, T upper) => throw _unsupported();
@override
void isIn(Iterable<T> values) { void isIn(Iterable<T> values) {
_raw = 'IN (' + values.map(_getValue).join(', ') + ')'; _raw = 'IN (' + values.map(_getValue).join(', ') + ')';
_hasValue = true; _hasValue = true;
} }
@override
void isNotIn(Iterable<T> values) { void isNotIn(Iterable<T> values) {
_raw = 'NOT IN (' + values.map(_getValue).join(', ') + ')'; _raw = 'NOT IN (' + values.map(_getValue).join(', ') + ')';
_hasValue = true; _hasValue = true;
@ -262,7 +248,6 @@ class StringSqlExpressionBuilder extends SqlExpressionBuilder<String> {
_hasValue = true; _hasValue = true;
} }
@override
void isBetween(String lower, String upper) { void isBetween(String lower, String upper) {
query.substitutionValues[lowerName] = lower; query.substitutionValues[lowerName] = lower;
query.substitutionValues[upperName] = upper; query.substitutionValues[upperName] = upper;
@ -270,7 +255,6 @@ class StringSqlExpressionBuilder extends SqlExpressionBuilder<String> {
_hasValue = true; _hasValue = true;
} }
@override
void isNotBetween(String lower, String upper) { void isNotBetween(String lower, String upper) {
query.substitutionValues[lowerName] = lower; query.substitutionValues[lowerName] = lower;
query.substitutionValues[upperName] = upper; query.substitutionValues[upperName] = upper;
@ -288,13 +272,11 @@ class StringSqlExpressionBuilder extends SqlExpressionBuilder<String> {
')'; ')';
} }
@override
void isIn(Iterable<String> values) { void isIn(Iterable<String> values) {
_raw = _in(values); _raw = _in(values);
_hasValue = true; _hasValue = true;
} }
@override
void isNotIn(Iterable<String> values) { void isNotIn(Iterable<String> values) {
_raw = 'NOT ' + _in(values); _raw = 'NOT ' + _in(values);
_hasValue = true; _hasValue = true;
@ -338,26 +320,6 @@ class BooleanSqlExpressionBuilder extends SqlExpressionBuilder<bool> {
void notEquals(bool value) { void notEquals(bool value) {
_change('!=', 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;
}
} }
class DateTimeSqlExpressionBuilder extends SqlExpressionBuilder<DateTime> { class DateTimeSqlExpressionBuilder extends SqlExpressionBuilder<DateTime> {
@ -425,27 +387,23 @@ class DateTimeSqlExpressionBuilder extends SqlExpressionBuilder<DateTime> {
_change('>=', value, includeTime != false); _change('>=', value, includeTime != false);
} }
@override
void isIn(Iterable<DateTime> values) { void isIn(Iterable<DateTime> values) {
_raw = '$columnName IN (' + _raw = '$columnName IN (' +
values.map(dateYmdHms.format).map((s) => '$s').join(', ') + values.map(dateYmdHms.format).map((s) => '$s').join(', ') +
')'; ')';
} }
@override
void isNotIn(Iterable<DateTime> values) { void isNotIn(Iterable<DateTime> values) {
_raw = '$columnName NOT IN (' + _raw = '$columnName NOT IN (' +
values.map(dateYmdHms.format).map((s) => '$s').join(', ') + values.map(dateYmdHms.format).map((s) => '$s').join(', ') +
')'; ')';
} }
@override
void isBetween(DateTime lower, DateTime upper) { void isBetween(DateTime lower, DateTime upper) {
var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper); var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper);
_raw = "$columnName BETWEEN '$l' and '$u'"; _raw = "$columnName BETWEEN '$l' and '$u'";
} }
@override
void isNotBetween(DateTime lower, DateTime upper) { void isNotBetween(DateTime lower, DateTime upper) {
var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper); var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper);
_raw = "$columnName NOT BETWEEN '$l' and '$u'"; _raw = "$columnName NOT BETWEEN '$l' and '$u'";
@ -471,60 +429,89 @@ class DateTimeSqlExpressionBuilder extends SqlExpressionBuilder<DateTime> {
} }
} }
class MapSqlExpressionBuilder extends SqlExpressionBuilder { abstract class JsonSqlExpressionBuilder<T, K> extends SqlExpressionBuilder<T> {
final List<JsonSqlExpressionBuilderProperty> _properties = [];
bool _hasValue = false; bool _hasValue = false;
Map _value; T _value;
String _op; String _op;
String _raw; String _raw;
MapSqlExpressionBuilder(Query query, String columnName) JsonSqlExpressionBuilder(Query query, String columnName)
: super(query, columnName); : super(query, columnName);
MapSqlExpressionBuilderProperty operator [](String name) { JsonSqlExpressionBuilderProperty operator [](K name) {
return MapSqlExpressionBuilderProperty(this, name); var p = _property(name);
_properties.add(p);
return p;
} }
bool get hasRaw => _raw != null; JsonSqlExpressionBuilderProperty _property(K name);
bool get hasRaw => _raw != null || _properties.any((p) => p.hasValue);
@override @override
bool get hasValue => _hasValue; bool get hasValue => _hasValue || _properties.any((p) => p.hasValue);
UnsupportedError _unsupported() => _encodeValue(T v) => v;
UnsupportedError('JSON/JSONB does not support this operation.');
void _append(SqlExpressionBuilder b) { bool _change(String op, T value) {
var c = b.compile();
if (c != null) {
_hasValue = true;
_raw ??= '';
if (b is! DateTimeSqlExpressionBuilder) {
_raw += '${b.columnName} ';
}
_raw += c;
}
}
bool _change(String op, Map value) {
_raw = null; _raw = null;
_op = op; _op = op;
_value = value; _value = value;
query.substitutionValues[substitution] = _value; query.substitutionValues[substitution] = _encodeValue(_value);
return _hasValue = true; return _hasValue = true;
} }
@override @override
String compile() { String compile() {
var s = _compile();
if (!_properties.any((p) => p.hasValue)) return s;
s ??= '';
for (var p in _properties) {
if (p.hasValue) {
var c = p.compile();
if (c != null) {
_hasValue = true;
s ??= '';
if (p.typed is! DateTimeSqlExpressionBuilder) {
s += '${p.typed.columnName} ';
}
s += c;
}
}
}
return s;
}
String _compile() {
if (_raw != null) return _raw; if (_raw != null) return _raw;
if (_value == null) return null; if (_value == null) return null;
return "::jsonb $_op @$substitution::jsonb"; return "::jsonb $_op @$substitution::jsonb";
} }
void contains(Map value) { void contains(T value) {
_change('@>', value); _change('@>', value);
} }
void equals(T value) {
_change('=', value);
}
}
class MapSqlExpressionBuilder extends JsonSqlExpressionBuilder<Map, String> {
MapSqlExpressionBuilder(Query query, String columnName)
: super(query, columnName);
@override
JsonSqlExpressionBuilderProperty _property(String name) {
return JsonSqlExpressionBuilderProperty(this, name, false);
}
void containsKey(String key) { void containsKey(String key) {
this[key].isNotNull(); this[key].isNotNull();
} }
@ -532,89 +519,96 @@ class MapSqlExpressionBuilder extends SqlExpressionBuilder {
void containsPair(key, value) { void containsPair(key, value) {
contains({key: value}); contains({key: value});
} }
void equals(Map value) {
_change('=', value);
}
@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();
} }
class MapSqlExpressionBuilderProperty { class ListSqlExpressionBuilder extends JsonSqlExpressionBuilder<List, int> {
final MapSqlExpressionBuilder builder; ListSqlExpressionBuilder(Query query, String columnName)
final String name; : super(query, columnName);
MapSqlExpressionBuilderProperty(this.builder, this.name); @override
_encodeValue(List v) => json.encode(v);
@override
JsonSqlExpressionBuilderProperty _property(int name) {
return JsonSqlExpressionBuilderProperty(this, name.toString(), true);
}
}
class JsonSqlExpressionBuilderProperty {
final JsonSqlExpressionBuilder builder;
final String name;
final bool isInt;
SqlExpressionBuilder _typed;
JsonSqlExpressionBuilderProperty(this.builder, this.name, this.isInt);
SqlExpressionBuilder get typed => _typed;
bool get hasValue => _typed?.hasValue == true;
String compile() => _typed?.compile();
T _set<T extends SqlExpressionBuilder>(T Function() value) {
if (_typed is T) {
return _typed as T;
} else if (_typed != null) {
throw StateError(
'$nameString is already typed as $_typed, and cannot be changed.');
} else {
_typed = value().._isProperty = true;
return _typed as T;
}
}
String get nameString {
if (isInt) {
return '(${builder.columnName}->>$name)::jsonb';
} else {
return "(${builder.columnName}->>'$name')::jsonb";
}
}
void isNotNull() { void isNotNull() {
builder builder
.._hasValue = true .._hasValue = true
.._raw ??= '' .._raw ??= ''
.._raw += "${builder.columnName}->>'$name' IS NOT NULL"; .._raw += "$nameString IS NOT NULL";
} }
void isNull() { void isNull() {
builder builder
.._hasValue = true .._hasValue = true
.._raw ??= '' .._raw ??= ''
.._raw += "${builder.columnName}->>'$name' IS NULL"; .._raw += "$nameString IS NULL";
} }
void asString(void Function(StringSqlExpressionBuilder) f) { StringSqlExpressionBuilder get asString {
var b = StringSqlExpressionBuilder( return _set(() => StringSqlExpressionBuilder(builder.query, nameString));
builder.query, "${builder.columnName}->>'$name'")
.._isProperty = true;
f(b);
builder._append(b);
} }
void asBool(void Function(BooleanSqlExpressionBuilder) f) { BooleanSqlExpressionBuilder get asBool {
var b = BooleanSqlExpressionBuilder( return _set(() => BooleanSqlExpressionBuilder(builder.query, nameString));
builder.query, "${builder.columnName}->>'$name'")
.._isProperty = true;
f(b);
builder._append(b);
} }
void asDateTime(void Function(DateTimeSqlExpressionBuilder) f) { DateTimeSqlExpressionBuilder get asDateTime {
var b = DateTimeSqlExpressionBuilder( return _set(() => DateTimeSqlExpressionBuilder(builder.query, nameString));
builder.query, "${builder.columnName}->>'$name'")
.._isProperty = true;
f(b);
builder._append(b);
} }
void asDouble(void Function(NumericSqlExpressionBuilder<double>) f) { NumericSqlExpressionBuilder<double> get asDouble {
var b = NumericSqlExpressionBuilder<double>( return _set(
builder.query, "${builder.columnName}->>'$name'") () => NumericSqlExpressionBuilder<double>(builder.query, nameString));
.._isProperty = true;
f(b);
builder._append(b);
} }
void asInt(void Function(NumericSqlExpressionBuilder<int>) f) { NumericSqlExpressionBuilder<int> get asInt {
var b = NumericSqlExpressionBuilder<int>( return _set(
builder.query, "${builder.columnName}->>'$name'") () => NumericSqlExpressionBuilder<int>(builder.query, nameString));
.._isProperty = true;
f(b);
builder._append(b);
} }
void asMap(void Function(MapSqlExpressionBuilder) f) { MapSqlExpressionBuilder get asMap {
var b = MapSqlExpressionBuilder( return _set(() => MapSqlExpressionBuilder(builder.query, nameString));
builder.query, "${builder.columnName}->>'$name'") }
.._isProperty = true;
f(b); ListSqlExpressionBuilder get asList {
builder._append(b); return _set(() => ListSqlExpressionBuilder(builder.query, nameString));
} }
} }

View file

@ -341,8 +341,19 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
} }
abstract class QueryValues { abstract class QueryValues {
Map<String, String> get casts => {};
Map<String, dynamic> toMap(); Map<String, dynamic> toMap();
String applyCast(String name, String sub) {
if (casts.containsKey(name)) {
var type = casts[name];
return 'CAST ($sub as $type)';
} else {
return sub;
}
}
String compileInsert(Query query, String tableName) { String compileInsert(Query query, String tableName) {
var data = toMap(); var data = toMap();
if (data.isEmpty) return null; if (data.isEmpty) return null;
@ -355,8 +366,9 @@ abstract class QueryValues {
if (i++ > 0) b.write(', '); if (i++ > 0) b.write(', ');
var name = query.reserveName(entry.key); var name = query.reserveName(entry.key);
var s = applyCast(entry.key, '@$name');
query.substitutionValues[name] = entry.value; query.substitutionValues[name] = entry.value;
b.write('@$name'); b.write(s);
} }
b.write(')'); b.write(')');
@ -376,8 +388,9 @@ abstract class QueryValues {
b.write('='); b.write('=');
var name = query.reserveName(entry.key); var name = query.reserveName(entry.key);
var s = applyCast(entry.key, '@$name');
query.substitutionValues[name] = entry.value; query.substitutionValues[name] = entry.value;
b.write('@$name'); b.write(s);
} }
return b.toString(); return b.toString();
} }
@ -421,7 +434,7 @@ abstract class QueryWhere {
if (builder.hasValue) { if (builder.hasValue) {
if (i++ > 0) b.write(' AND '); if (i++ > 0) b.write(' AND ');
if (builder is DateTimeSqlExpressionBuilder || if (builder is DateTimeSqlExpressionBuilder ||
(builder is MapSqlExpressionBuilder && builder.hasRaw)) { (builder is JsonSqlExpressionBuilder && builder.hasRaw)) {
if (tableName != null) b.write('$tableName.'); if (tableName != null) b.write('$tableName.');
b.write(builder.compile()); b.write(builder.compile());
} else { } else {

View file

@ -1,5 +1,5 @@
name: angel_orm name: angel_orm
version: 2.0.0-dev.17 version: 2.0.0-dev.18
description: Runtime support for Angel's ORM. Includes base classes for queries. description: Runtime support for Angel's ORM. Includes base classes for queries.
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

View file

@ -1,3 +1,6 @@
# 2.0.0-dev.4
* List generation support.
# 2.0.0-dev.3 # 2.0.0-dev.3
* Add JSON/JSONB support for Maps. * Add JSON/JSONB support for Maps.

View file

@ -217,6 +217,8 @@ ColumnType inferColumnType(DartType type) {
return ColumnType.timeStamp; return ColumnType.timeStamp;
if (const TypeChecker.fromRuntime(Map).isAssignableFromType(type)) if (const TypeChecker.fromRuntime(Map).isAssignableFromType(type))
return ColumnType.jsonb; return ColumnType.jsonb;
if (const TypeChecker.fromRuntime(List).isAssignableFromType(type))
return ColumnType.jsonb;
if (type is InterfaceType && type.element.isEnum) return ColumnType.int; if (type is InterfaceType && type.element.isEnum) return ColumnType.int;
return null; return null;
} }

View file

@ -515,6 +515,9 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
} else if (const TypeChecker.fromRuntime(Map) } else if (const TypeChecker.fromRuntime(Map)
.isAssignableFromType(type)) { .isAssignableFromType(type)) {
builderType = refer('MapSqlExpressionBuilder'); builderType = refer('MapSqlExpressionBuilder');
} else if (const TypeChecker.fromRuntime(List)
.isAssignableFromType(type)) {
builderType = refer('ListSqlExpressionBuilder');
} 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)
@ -565,6 +568,30 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
..name = '${rc.pascalCase}QueryValues' ..name = '${rc.pascalCase}QueryValues'
..extend = refer('MapQueryValues'); ..extend = refer('MapQueryValues');
// Override casts so that we can cast Lists
clazz.methods.add(Method((b) {
b
..name = 'casts'
..annotations.add(refer('override'))
..type = MethodType.getter
..body = Block((b) {
var args = <String, Expression>{};
for (var field in ctx.effectiveFields) {
var fType = field.type;
var name = ctx.buildContext.resolveFieldName(field.name);
var type = ctx.columns[field.name]?.type?.name;
if (type == null) continue;
if (const TypeChecker.fromRuntime(List)
.isAssignableFromType(fType)) {
args[name] = literalString(type);
}
}
b.addExpression(literalMap(args).returned);
});
}));
// 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 fType = field.type; var fType = field.type;
@ -580,6 +607,11 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
var asInt = value.asA(refer('int')); var asInt = value.asA(refer('int'));
var t = convertTypeReference(fType); var t = convertTypeReference(fType);
value = t.property('values').index(asInt); value = t.property('values').index(asInt);
} else if (const TypeChecker.fromRuntime(List)
.isAssignableFromType(fType)) {
value = refer('json')
.property('decode')
.call([value.asA(refer('String'))]).asA(refer('List'));
} else { } else {
value = value.asA(type); value = value.asA(type);
} }
@ -596,6 +628,9 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
if (fType is InterfaceType && fType.element.isEnum) { if (fType is InterfaceType && fType.element.isEnum) {
value = value.property('index'); value = value.property('index');
} else if (const TypeChecker.fromRuntime(List)
.isAssignableFromType(fType)) {
value = refer('json').property('encode').call([value]);
} }
b b
@ -618,18 +653,13 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
..name = 'model' ..name = 'model'
..type = ctx.buildContext.modelClassType)) ..type = ctx.buildContext.modelClassType))
..body = new Block((b) { ..body = new Block((b) {
var args = <String, Expression>{};
for (var field in ctx.effectiveFields) { for (var field in ctx.effectiveFields) {
if (isSpecialId(ctx, field) || field is RelationFieldImpl) if (isSpecialId(ctx, field) || field is RelationFieldImpl)
continue; continue;
args[ctx.buildContext.resolveFieldName(field.name)] = b.addExpression(refer(field.name)
refer('model').property(field.name); .assign(refer('model').property(field.name)));
} }
b.addExpression(
refer('values').property('addAll').call([literalMap(args)]));
for (var field in ctx.effectiveFields) { for (var field in ctx.effectiveFields) {
if (field is RelationFieldImpl) { if (field is RelationFieldImpl) {
var original = field.originalFieldName; var original = field.originalFieldName;

View file

@ -1,5 +1,5 @@
name: angel_orm_generator name: angel_orm_generator
version: 2.0.0-dev.3 version: 2.0.0-dev.4
description: Code generators for Angel's ORM. Generates query builder classes. description: Code generators for Angel's ORM. Generates query builder classes.
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

View file

@ -35,6 +35,7 @@ class PostgresExecutor extends QueryExecutor {
if (!Platform.environment.containsKey('STFU')) { if (!Platform.environment.containsKey('STFU')) {
print('Running: $query'); print('Running: $query');
if (substitutionValues.isNotEmpty) print('Values: $substitutionValues'); if (substitutionValues.isNotEmpty) print('Values: $substitutionValues');
print(substitutionValues.map((k, v) => MapEntry(k, v.runtimeType)));
} }
return connection.query(query, substitutionValues: substitutionValues); return connection.query(query, substitutionValues: substitutionValues);
} }

View file

@ -10,14 +10,20 @@ main() {
}); });
test('insert', () async { test('insert', () async {
var query = HasMapQuery()..values.value = {'foo': 'bar'}; var query = HasMapQuery();
query.values
..value = {'foo': 'bar'}
..list = ['1', 2, 3.0];
var model = await query.insert(executor); var model = await query.insert(executor);
print(model.toJson()); print(model.toJson());
expect(model, HasMap(value: {'foo': 'bar'})); expect(model, HasMap(value: {'foo': 'bar'}, list: ['1', 2, 3.0]));
}); });
test('insert', () async { test('update', () async {
var query = HasMapQuery()..values.value = {'foo': 'bar'}; var query = HasMapQuery();
query.values
..value = {'foo': 'bar'}
..list = ['1', 2, 3.0];
var model = await query.insert(executor); var model = await query.insert(executor);
print(model.toJson()); print(model.toJson());
@ -29,7 +35,10 @@ main() {
HasMap initialValue; HasMap initialValue;
setUp(() async { setUp(() async {
var query = HasMapQuery()..values.value = {'foo': 'bar'}; var query = HasMapQuery();
query.values
..value = {'foo': 'bar'}
..list = ['1', 2, 3.0];
initialValue = await query.insert(executor); initialValue = await query.insert(executor);
}); });
@ -42,15 +51,35 @@ main() {
var query = HasMapQuery(); var query = HasMapQuery();
query.where.value.equals({'foo': 'bar'}); query.where.value.equals({'foo': 'bar'});
expect(await query.get(executor), [initialValue]); expect(await query.get(executor), [initialValue]);
query = HasMapQuery();
query.where.value.equals({'foo': 'baz'});
expect(await query.get(executor), isEmpty);
}); });
test('property equals', () async { test('list equals', () async {
var query = HasMapQuery(); var query = HasMapQuery();
query.where.value['foo'].asString((b) => b.equals('bar')); query.where.list.equals(['1', 2, 3.0]);
expect(await query.get(executor), [initialValue]); expect(await query.get(executor), [initialValue]);
query = HasMapQuery(); query = HasMapQuery();
query.where.value['foo'].asString((b) => b.equals('baz')); query.where.list.equals(['10', 20, 30.0]);
expect(await query.get(executor), isEmpty);
});
test('property equals', () async {
var query = HasMapQuery()..where.value['foo'].asString.equals('bar');
expect(await query.get(executor), [initialValue]);
query = HasMapQuery()..where.value['foo'].asString.equals('baz');
expect(await query.get(executor), []);
});
test('index equals', () async {
var query = HasMapQuery()..where.list[0].asString.equals('1');
expect(await query.get(executor), [initialValue]);
query = HasMapQuery()..where.list[1].asInt.equals(3);
expect(await query.get(executor), []); expect(await query.get(executor), []);
}); });
}); });

View file

@ -1,6 +1,7 @@
CREATE TEMPORARY TABLE "has_maps" ( CREATE TEMPORARY TABLE "has_maps" (
id serial PRIMARY KEY, id serial PRIMARY KEY,
value jsonb not null, value jsonb not null,
list jsonb not null,
created_at timestamp, created_at timestamp,
updated_at timestamp updated_at timestamp
); );

View file

@ -95,6 +95,11 @@ class AuthorQueryWhere extends QueryWhere {
} }
class AuthorQueryValues extends MapQueryValues { class AuthorQueryValues extends MapQueryValues {
@override
get casts {
return {};
}
int get id { int get id {
return (values['id'] as int); return (values['id'] as int);
} }
@ -116,11 +121,9 @@ class AuthorQueryValues extends MapQueryValues {
set updatedAt(DateTime value) => values['updated_at'] = value; set updatedAt(DateTime value) => values['updated_at'] = value;
void copyFrom(Author model) { void copyFrom(Author model) {
values.addAll({ name = model.name;
'name': model.name, createdAt = model.createdAt;
'created_at': model.createdAt, updatedAt = model.updatedAt;
'updated_at': model.updatedAt
});
} }
} }

View file

@ -123,6 +123,11 @@ class BookQueryWhere extends QueryWhere {
} }
class BookQueryValues extends MapQueryValues { class BookQueryValues extends MapQueryValues {
@override
get casts {
return {};
}
int get id { int get id {
return (values['id'] as int); return (values['id'] as int);
} }
@ -154,11 +159,9 @@ class BookQueryValues extends MapQueryValues {
set updatedAt(DateTime value) => values['updated_at'] = value; set updatedAt(DateTime value) => values['updated_at'] = value;
void copyFrom(Book model) { void copyFrom(Book model) {
values.addAll({ name = model.name;
'name': model.name, createdAt = model.createdAt;
'created_at': model.createdAt, updatedAt = model.updatedAt;
'updated_at': model.updatedAt
});
if (model.author != null) { if (model.author != null) {
values['author_id'] = int.parse(model.author.id); values['author_id'] = int.parse(model.author.id);
} }

View file

@ -127,6 +127,11 @@ class CarQueryWhere extends QueryWhere {
} }
class CarQueryValues extends MapQueryValues { class CarQueryValues extends MapQueryValues {
@override
get casts {
return {};
}
int get id { int get id {
return (values['id'] as int); return (values['id'] as int);
} }
@ -163,14 +168,12 @@ class CarQueryValues extends MapQueryValues {
set updatedAt(DateTime value) => values['updated_at'] = value; set updatedAt(DateTime value) => values['updated_at'] = value;
void copyFrom(Car model) { void copyFrom(Car model) {
values.addAll({ make = model.make;
'make': model.make, description = model.description;
'description': model.description, familyFriendly = model.familyFriendly;
'family_friendly': model.familyFriendly, recalledAt = model.recalledAt;
'recalled_at': model.recalledAt, createdAt = model.createdAt;
'created_at': model.createdAt, updatedAt = model.updatedAt;
'updated_at': model.updatedAt
});
} }
} }

View file

@ -90,6 +90,11 @@ class CustomerQueryWhere extends QueryWhere {
} }
class CustomerQueryValues extends MapQueryValues { class CustomerQueryValues extends MapQueryValues {
@override
get casts {
return {};
}
int get id { int get id {
return (values['id'] as int); return (values['id'] as int);
} }
@ -106,8 +111,8 @@ class CustomerQueryValues extends MapQueryValues {
set updatedAt(DateTime value) => values['updated_at'] = value; set updatedAt(DateTime value) => values['updated_at'] = value;
void copyFrom(Customer model) { void copyFrom(Customer model) {
values createdAt = model.createdAt;
.addAll({'created_at': model.createdAt, 'updated_at': model.updatedAt}); updatedAt = model.updatedAt;
} }
} }

View file

@ -100,6 +100,11 @@ class FootQueryWhere extends QueryWhere {
} }
class FootQueryValues extends MapQueryValues { class FootQueryValues extends MapQueryValues {
@override
get casts {
return {};
}
int get id { int get id {
return (values['id'] as int); return (values['id'] as int);
} }
@ -126,12 +131,10 @@ class FootQueryValues extends MapQueryValues {
set updatedAt(DateTime value) => values['updated_at'] = value; set updatedAt(DateTime value) => values['updated_at'] = value;
void copyFrom(Foot model) { void copyFrom(Foot model) {
values.addAll({ legId = model.legId;
'leg_id': model.legId, nToes = model.nToes;
'n_toes': model.nToes, createdAt = model.createdAt;
'created_at': model.createdAt, updatedAt = model.updatedAt;
'updated_at': model.updatedAt
});
} }
} }

View file

@ -100,6 +100,11 @@ class FruitQueryWhere extends QueryWhere {
} }
class FruitQueryValues extends MapQueryValues { class FruitQueryValues extends MapQueryValues {
@override
get casts {
return {};
}
int get id { int get id {
return (values['id'] as int); return (values['id'] as int);
} }
@ -126,12 +131,10 @@ class FruitQueryValues extends MapQueryValues {
set updatedAt(DateTime value) => values['updated_at'] = value; set updatedAt(DateTime value) => values['updated_at'] = value;
void copyFrom(Fruit model) { void copyFrom(Fruit model) {
values.addAll({ treeId = model.treeId;
'tree_id': model.treeId, commonName = model.commonName;
'common_name': model.commonName, createdAt = model.createdAt;
'created_at': model.createdAt, updatedAt = model.updatedAt;
'updated_at': model.updatedAt
});
} }
} }

View file

@ -96,6 +96,11 @@ class HasCarQueryWhere extends QueryWhere {
} }
class HasCarQueryValues extends MapQueryValues { class HasCarQueryValues extends MapQueryValues {
@override
get casts {
return {};
}
int get id { int get id {
return (values['id'] as int); return (values['id'] as int);
} }
@ -117,11 +122,9 @@ class HasCarQueryValues extends MapQueryValues {
set updatedAt(DateTime value) => values['updated_at'] = value; set updatedAt(DateTime value) => values['updated_at'] = value;
void copyFrom(HasCar model) { void copyFrom(HasCar model) {
values.addAll({ type = model.type;
'type': model.type, createdAt = model.createdAt;
'created_at': model.createdAt, updatedAt = model.updatedAt;
'updated_at': model.updatedAt
});
} }
} }

View file

@ -10,4 +10,6 @@ part 'has_map.g.dart';
@serializable @serializable
abstract class _HasMap { abstract class _HasMap {
Map get value; Map get value;
List get list;
} }

View file

@ -11,6 +11,7 @@ class HasMapMigration extends Migration {
up(Schema schema) { up(Schema schema) {
schema.create('has_maps', (table) { schema.create('has_maps', (table) {
table.declare('value', new ColumnType('jsonb')); table.declare('value', new ColumnType('jsonb'));
table.declare('list', new ColumnType('jsonb'));
}); });
} }
@ -41,7 +42,7 @@ class HasMapQuery extends Query<HasMap, HasMapQueryWhere> {
@override @override
get fields { get fields {
return const ['value']; return const ['value', 'list'];
} }
@override @override
@ -56,7 +57,9 @@ class HasMapQuery extends Query<HasMap, HasMapQueryWhere> {
static HasMap parseRow(List row) { static HasMap parseRow(List row) {
if (row.every((x) => x == null)) return null; if (row.every((x) => x == null)) return null;
var model = new HasMap(value: (row[0] as Map<dynamic, dynamic>)); var model = new HasMap(
value: (row[0] as Map<dynamic, dynamic>),
list: (row[1] as List<dynamic>));
return model; return model;
} }
@ -68,24 +71,38 @@ class HasMapQuery extends Query<HasMap, HasMapQueryWhere> {
class HasMapQueryWhere extends QueryWhere { class HasMapQueryWhere extends QueryWhere {
HasMapQueryWhere(HasMapQuery query) HasMapQueryWhere(HasMapQuery query)
: value = new MapSqlExpressionBuilder(query, 'value'); : value = new MapSqlExpressionBuilder(query, 'value'),
list = new ListSqlExpressionBuilder(query, 'list');
final MapSqlExpressionBuilder value; final MapSqlExpressionBuilder value;
final ListSqlExpressionBuilder list;
@override @override
get expressionBuilders { get expressionBuilders {
return [value]; return [value, list];
} }
} }
class HasMapQueryValues extends MapQueryValues { class HasMapQueryValues extends MapQueryValues {
@override
get casts {
return {'list': 'jsonb'};
}
Map<dynamic, dynamic> get value { Map<dynamic, dynamic> get value {
return (values['value'] as Map<dynamic, dynamic>); return (values['value'] as Map<dynamic, dynamic>);
} }
set value(Map<dynamic, dynamic> value) => values['value'] = value; set value(Map<dynamic, dynamic> value) => values['value'] = value;
List<dynamic> get list {
return (json.decode((values['list'] as String)) as List);
}
set list(List<dynamic> value) => values['list'] = json.encode(value);
void copyFrom(HasMap model) { void copyFrom(HasMap model) {
values.addAll({'value': model.value}); value = model.value;
list = model.list;
} }
} }
@ -95,25 +112,30 @@ class HasMapQueryValues extends MapQueryValues {
@generatedSerializable @generatedSerializable
class HasMap implements _HasMap { class HasMap implements _HasMap {
const HasMap({Map<dynamic, dynamic> this.value}); const HasMap({Map<dynamic, dynamic> this.value, List<dynamic> this.list});
@override @override
final Map<dynamic, dynamic> value; final Map<dynamic, dynamic> value;
HasMap copyWith({Map<dynamic, dynamic> value}) { @override
return new HasMap(value: value ?? this.value); final List<dynamic> list;
HasMap copyWith({Map<dynamic, dynamic> value, List<dynamic> list}) {
return new HasMap(value: value ?? this.value, list: list ?? this.list);
} }
bool operator ==(other) { bool operator ==(other) {
return other is _HasMap && return other is _HasMap &&
const MapEquality<dynamic, dynamic>( const MapEquality<dynamic, dynamic>(
keys: const DefaultEquality(), values: const DefaultEquality()) keys: const DefaultEquality(), values: const DefaultEquality())
.equals(other.value, value); .equals(other.value, value) &&
const ListEquality<dynamic>(const DefaultEquality())
.equals(other.list, list);
} }
@override @override
int get hashCode { int get hashCode {
return hashObjects([value]); return hashObjects([value, list]);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -130,6 +152,9 @@ abstract class HasMapSerializer {
return new HasMap( return new HasMap(
value: map['value'] is Map value: map['value'] is Map
? (map['value'] as Map).cast<dynamic, dynamic>() ? (map['value'] as Map).cast<dynamic, dynamic>()
: null,
list: map['list'] is Iterable
? (map['list'] as Iterable).cast<dynamic>().toList()
: null); : null);
} }
@ -137,12 +162,14 @@ abstract class HasMapSerializer {
if (model == null) { if (model == null) {
return null; return null;
} }
return {'value': model.value}; return {'value': model.value, 'list': model.list};
} }
} }
abstract class HasMapFields { abstract class HasMapFields {
static const List<String> allFields = const <String>[value]; static const List<String> allFields = const <String>[value, list];
static const String value = 'value'; static const String value = 'value';
static const String list = 'list';
} }

View file

@ -104,6 +104,11 @@ class LegQueryWhere extends QueryWhere {
} }
class LegQueryValues extends MapQueryValues { class LegQueryValues extends MapQueryValues {
@override
get casts {
return {};
}
int get id { int get id {
return (values['id'] as int); return (values['id'] as int);
} }
@ -125,11 +130,9 @@ class LegQueryValues extends MapQueryValues {
set updatedAt(DateTime value) => values['updated_at'] = value; set updatedAt(DateTime value) => values['updated_at'] = value;
void copyFrom(Leg model) { void copyFrom(Leg model) {
values.addAll({ name = model.name;
'name': model.name, createdAt = model.createdAt;
'created_at': model.createdAt, updatedAt = model.updatedAt;
'updated_at': model.updatedAt
});
} }
} }

View file

@ -131,6 +131,11 @@ class OrderQueryWhere extends QueryWhere {
} }
class OrderQueryValues extends MapQueryValues { class OrderQueryValues extends MapQueryValues {
@override
get casts {
return {};
}
int get id { int get id {
return (values['id'] as int); return (values['id'] as int);
} }
@ -167,13 +172,11 @@ class OrderQueryValues extends MapQueryValues {
set updatedAt(DateTime value) => values['updated_at'] = value; set updatedAt(DateTime value) => values['updated_at'] = value;
void copyFrom(Order model) { void copyFrom(Order model) {
values.addAll({ employeeId = model.employeeId;
'employee_id': model.employeeId, orderDate = model.orderDate;
'order_date': model.orderDate, shipperId = model.shipperId;
'shipper_id': model.shipperId, createdAt = model.createdAt;
'created_at': model.createdAt, updatedAt = model.updatedAt;
'updated_at': model.updatedAt
});
if (model.customer != null) { if (model.customer != null) {
values['customer_id'] = int.parse(model.customer.id); values['customer_id'] = int.parse(model.customer.id);
} }

View file

@ -165,6 +165,11 @@ class TreeQueryWhere extends QueryWhere {
} }
class TreeQueryValues extends MapQueryValues { class TreeQueryValues extends MapQueryValues {
@override
get casts {
return {};
}
int get id { int get id {
return (values['id'] as int); return (values['id'] as int);
} }
@ -186,11 +191,9 @@ class TreeQueryValues extends MapQueryValues {
set updatedAt(DateTime value) => values['updated_at'] = value; set updatedAt(DateTime value) => values['updated_at'] = value;
void copyFrom(Tree model) { void copyFrom(Tree model) {
values.addAll({ rings = model.rings;
'rings': model.rings, createdAt = model.createdAt;
'created_at': model.createdAt, updatedAt = model.updatedAt;
'updated_at': model.updatedAt
});
} }
} }

View file

@ -147,6 +147,11 @@ class UserQueryWhere extends QueryWhere {
} }
class UserQueryValues extends MapQueryValues { class UserQueryValues extends MapQueryValues {
@override
get casts {
return {};
}
int get id { int get id {
return (values['id'] as int); return (values['id'] as int);
} }
@ -178,13 +183,11 @@ class UserQueryValues extends MapQueryValues {
set updatedAt(DateTime value) => values['updated_at'] = value; set updatedAt(DateTime value) => values['updated_at'] = value;
void copyFrom(User model) { void copyFrom(User model) {
values.addAll({ username = model.username;
'username': model.username, password = model.password;
'password': model.password, email = model.email;
'email': model.email, createdAt = model.createdAt;
'created_at': model.createdAt, updatedAt = model.updatedAt;
'updated_at': model.updatedAt
});
} }
} }
@ -273,6 +276,11 @@ class RoleUserQueryWhere extends QueryWhere {
} }
class RoleUserQueryValues extends MapQueryValues { class RoleUserQueryValues extends MapQueryValues {
@override
get casts {
return {};
}
int get id { int get id {
return (values['id'] as int); return (values['id'] as int);
} }
@ -299,8 +307,8 @@ class RoleUserQueryValues extends MapQueryValues {
set updatedAt(DateTime value) => values['updated_at'] = value; set updatedAt(DateTime value) => values['updated_at'] = value;
void copyFrom(RoleUser model) { void copyFrom(RoleUser model) {
values createdAt = model.createdAt;
.addAll({'created_at': model.createdAt, 'updated_at': model.updatedAt}); updatedAt = model.updatedAt;
if (model.role != null) { if (model.role != null) {
values['role_id'] = int.parse(model.role.id); values['role_id'] = int.parse(model.role.id);
} }
@ -378,6 +386,11 @@ class RoleQueryWhere extends QueryWhere {
} }
class RoleQueryValues extends MapQueryValues { class RoleQueryValues extends MapQueryValues {
@override
get casts {
return {};
}
int get id { int get id {
return (values['id'] as int); return (values['id'] as int);
} }
@ -399,11 +412,9 @@ class RoleQueryValues extends MapQueryValues {
set updatedAt(DateTime value) => values['updated_at'] = value; set updatedAt(DateTime value) => values['updated_at'] = value;
void copyFrom(Role model) { void copyFrom(Role model) {
values.addAll({ name = model.name;
'name': model.name, createdAt = model.createdAt;
'created_at': model.createdAt, updatedAt = model.updatedAt;
'updated_at': model.updatedAt
});
} }
} }