From cb73f4a112d474b6c8dee7fb87a0a8eb4d559997 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 1 Dec 2018 12:21:34 -0500 Subject: [PATCH] bring back older query builder --- angel_orm/example/main.angel_serialize.g.part | 67 +++ angel_orm/example/main.dart | 87 +++- angel_orm/example/main.g.dart | 71 +++ angel_orm/example/main.serializer.g.dart | 64 +++ angel_orm/lib/angel_orm.dart | 1 + angel_orm/lib/src/annotations.dart | 18 +- angel_orm/lib/src/builder.dart | 340 +++++++++++++++ angel_orm/lib/src/query.dart | 410 ++++-------------- angel_orm/pubspec.yaml | 5 +- 9 files changed, 721 insertions(+), 342 deletions(-) create mode 100644 angel_orm/example/main.angel_serialize.g.part create mode 100644 angel_orm/example/main.g.dart create mode 100644 angel_orm/example/main.serializer.g.dart create mode 100644 angel_orm/lib/src/builder.dart diff --git a/angel_orm/example/main.angel_serialize.g.part b/angel_orm/example/main.angel_serialize.g.part new file mode 100644 index 00000000..708a0a9d --- /dev/null +++ b/angel_orm/example/main.angel_serialize.g.part @@ -0,0 +1,67 @@ +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Employee extends _Employee { + Employee( + {this.id, + this.firstName, + this.lastName, + this.salary, + this.createdAt, + this.updatedAt}); + + @override + final String id; + + @override + final String firstName; + + @override + final String lastName; + + @override + final double salary; + + @override + final DateTime createdAt; + + @override + final DateTime updatedAt; + + Employee copyWith( + {String id, + String firstName, + String lastName, + double salary, + DateTime createdAt, + DateTime updatedAt}) { + return new Employee( + id: id ?? this.id, + firstName: firstName ?? this.firstName, + lastName: lastName ?? this.lastName, + salary: salary ?? this.salary, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt); + } + + bool operator ==(other) { + return other is _Employee && + other.id == id && + other.firstName == firstName && + other.lastName == lastName && + other.salary == salary && + other.createdAt == createdAt && + other.updatedAt == updatedAt; + } + + @override + int get hashCode { + return hashObjects([id, firstName, lastName, salary, createdAt, updatedAt]); + } + + Map toJson() { + return EmployeeSerializer.toMap(this); + } +} diff --git a/angel_orm/example/main.dart b/angel_orm/example/main.dart index 21f34ffc..75f76a0f 100644 --- a/angel_orm/example/main.dart +++ b/angel_orm/example/main.dart @@ -1,27 +1,92 @@ import 'package:angel_model/angel_model.dart'; import 'package:angel_orm/angel_orm.dart'; +import 'package:angel_serialize/angel_serialize.dart'; +part 'main.g.dart'; +part 'main.serializer.g.dart'; -main() { +main() async { + var query = new EmployeeQuery(); + query.where + ..firstName.equals('Rich') + ..lastName.equals('Person') + ..or(new EmployeeQueryWhere()..salary.greaterThanOrEqualTo(75000)); + var richPerson = await query.getOne(new _FakeExecutor()); + print(richPerson.toJson()); } -@postgreSqlOrm -abstract class Company extends Model { - String get name; +class _FakeExecutor extends QueryExecutor { + const _FakeExecutor(); - bool get isFortune500; + @override + Future> query(String query) async { + var now = new DateTime.now(); + print('_FakeExecutor received query: $query'); + return [ + [1, 'Rich', 'Person', 100000.0, now, now] + ]; + } } -@postgreSqlOrm +@orm +@serializable abstract class _Employee extends Model { - @belongsTo - Company get company; - String get firstName; String get lastName; double get salary; - - bool get isFortune500Employee => company.isFortune500; +} + +class EmployeeQuery extends Query { + @override + final EmployeeQueryWhere where = new EmployeeQueryWhere(); + + @override + String get tableName => 'employees'; + + @override + List get fields => + ['id', 'first_name', 'last_name', 'salary', 'created_at', 'updated_at']; + + @override + Employee deserialize(List row) { + return new Employee( + id: row[0].toString(), + firstName: row[1] as String, + lastName: row[2] as String, + salary: row[3] as double, + createdAt: row[4] as DateTime, + updatedAt: row[5] as DateTime); + } +} + +class EmployeeQueryWhere extends QueryWhere { + @override + Map get expressionBuilders { + return { + 'id': id, + 'first_name': firstName, + 'last_name': lastName, + 'salary': salary, + 'created_at': createdAt, + 'updated_at': updatedAt + }; + } + + final NumericSqlExpressionBuilder id = + new NumericSqlExpressionBuilder(); + + final StringSqlExpressionBuilder firstName = new StringSqlExpressionBuilder(); + + final StringSqlExpressionBuilder lastName = new StringSqlExpressionBuilder(); + + final NumericSqlExpressionBuilder salary = + new NumericSqlExpressionBuilder(); + + final DateTimeSqlExpressionBuilder createdAt = + new DateTimeSqlExpressionBuilder('created_at'); + + final DateTimeSqlExpressionBuilder updatedAt = + new DateTimeSqlExpressionBuilder('updated_at'); } diff --git a/angel_orm/example/main.g.dart b/angel_orm/example/main.g.dart new file mode 100644 index 00000000..950cbd12 --- /dev/null +++ b/angel_orm/example/main.g.dart @@ -0,0 +1,71 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'main.dart'; + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Employee extends _Employee { + Employee( + {this.id, + this.firstName, + this.lastName, + this.salary, + this.createdAt, + this.updatedAt}); + + @override + final String id; + + @override + final String firstName; + + @override + final String lastName; + + @override + final double salary; + + @override + final DateTime createdAt; + + @override + final DateTime updatedAt; + + Employee copyWith( + {String id, + String firstName, + String lastName, + double salary, + DateTime createdAt, + DateTime updatedAt}) { + return new Employee( + id: id ?? this.id, + firstName: firstName ?? this.firstName, + lastName: lastName ?? this.lastName, + salary: salary ?? this.salary, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt); + } + + bool operator ==(other) { + return other is _Employee && + other.id == id && + other.firstName == firstName && + other.lastName == lastName && + other.salary == salary && + other.createdAt == createdAt && + other.updatedAt == updatedAt; + } + + @override + int get hashCode { + return hashObjects([id, firstName, lastName, salary, createdAt, updatedAt]); + } + + Map toJson() { + return EmployeeSerializer.toMap(this); + } +} diff --git a/angel_orm/example/main.serializer.g.dart b/angel_orm/example/main.serializer.g.dart new file mode 100644 index 00000000..5e160b3b --- /dev/null +++ b/angel_orm/example/main.serializer.g.dart @@ -0,0 +1,64 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'main.dart'; + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +abstract class EmployeeSerializer { + static Employee fromMap(Map map) { + return new Employee( + id: map['id'] as String, + firstName: map['first_name'] as String, + lastName: map['last_name'] as String, + salary: map['salary'] as double, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null); + } + + static Map toMap(Employee model) { + if (model == null) { + return null; + } + return { + 'id': model.id, + 'first_name': model.firstName, + 'last_name': model.lastName, + 'salary': model.salary, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String() + }; + } +} + +abstract class EmployeeFields { + static const List allFields = const [ + id, + firstName, + lastName, + salary, + createdAt, + updatedAt + ]; + + static const String id = 'id'; + + static const String firstName = 'first_name'; + + static const String lastName = 'last_name'; + + static const String salary = 'salary'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; +} diff --git a/angel_orm/lib/angel_orm.dart b/angel_orm/lib/angel_orm.dart index 1af57ece..3ba225e9 100644 --- a/angel_orm/lib/angel_orm.dart +++ b/angel_orm/lib/angel_orm.dart @@ -1,4 +1,5 @@ export 'src/annotations.dart'; +export 'src/builder.dart'; export 'src/migration.dart'; export 'src/relations.dart'; export 'src/query.dart'; \ No newline at end of file diff --git a/angel_orm/lib/src/annotations.dart b/angel_orm/lib/src/annotations.dart index c5ac0266..5d784e07 100644 --- a/angel_orm/lib/src/annotations.dart +++ b/angel_orm/lib/src/annotations.dart @@ -1,23 +1,9 @@ -const Orm mongoDBOrm = const Orm(OrmType.mongoDB); - -const Orm rethinkDBOrm = const Orm(OrmType.rethinkDB); - -const Orm postgreSqlOrm = const Orm(OrmType.postgreSql); - -const Orm mySqlOrm = const Orm(OrmType.mySql); +const Orm orm = const Orm(); class Orm { - final OrmType type; final String tableName; - const Orm(this.type, {this.tableName}); -} - -enum OrmType { - mongoDB, - rethinkDB, - mySql, - postgreSql, + const Orm({this.tableName}); } class CanJoin { diff --git a/angel_orm/lib/src/builder.dart b/angel_orm/lib/src/builder.dart new file mode 100644 index 00000000..6fbe6547 --- /dev/null +++ b/angel_orm/lib/src/builder.dart @@ -0,0 +1,340 @@ +import 'package:intl/intl.dart' show DateFormat; +import 'package:string_scanner/string_scanner.dart'; + +final DateFormat dateYmd = new DateFormat('yyyy-MM-dd'); +final DateFormat dateYmdHms = new DateFormat('yyyy-MM-dd HH:mm:ss'); + +/// Cleans an input SQL expression of common SQL injection points. +String sanitizeExpression(String unsafe) { + var buf = new StringBuffer(); + var scanner = new StringScanner(unsafe); + int ch; + + while (!scanner.isDone) { + // Ignore comment starts + if (scanner.scan('--') || scanner.scan('/*')) + continue; + + // Ignore all single quotes and attempted escape sequences + else if (scanner.scan("'") || scanner.scan('\\')) + continue; + + // Otherwise, add the next char, unless it's a null byte. + else if ((ch = scanner.readChar()) != 0 && ch != null) + buf.writeCharCode(ch); + } + + return buf.toString(); +} + +abstract class SqlExpressionBuilder { + bool get hasValue; + + String compile(); + + void isBetween(T lower, T upper); + + void isNotBetween(T lower, T upper); + + void isIn(Iterable values); + + void isNotIn(Iterable values); +} + +class NumericSqlExpressionBuilder + implements SqlExpressionBuilder { + bool _hasValue = false; + String _op = '='; + String _raw; + T _value; + + @override + bool get hasValue => _hasValue; + + bool _change(String op, T value) { + _raw = null; + _op = op; + _value = value; + return _hasValue = true; + } + + @override + String compile() { + if (_raw != null) return _raw; + if (_value == null) return null; + return '$_op $_value'; + } + + operator <(T value) => _change('<', value); + + operator >(T value) => _change('>', value); + + operator <=(T value) => _change('<=', value); + + operator >=(T value) => _change('>=', value); + + void lessThan(T value) { + _change('<', value); + } + + void lessThanOrEqualTo(T value) { + _change('<=', value); + } + + void greaterThan(T value) { + _change('>', value); + } + + void greaterThanOrEqualTo(T value) { + _change('>=', value); + } + + void equals(T value) { + _change('=', value); + } + + void notEquals(T value) { + _change('!=', value); + } + + @override + void isBetween(T lower, T upper) { + _raw = 'BETWEEN $lower AND $upper'; + _hasValue = true; + } + + @override + void isNotBetween(T lower, T upper) { + _raw = 'NOT BETWEEN $lower AND $upper'; + _hasValue = true; + } + + @override + void isIn(Iterable values) { + _raw = 'IN (' + values.join(', ') + ')'; + _hasValue = true; + } + + @override + void isNotIn(Iterable values) { + _raw = 'NOT IN (' + values.join(', ') + ')'; + _hasValue = true; + } +} + +class StringSqlExpressionBuilder implements SqlExpressionBuilder { + bool _hasValue = false; + String _op = '=', _raw, _value; + + @override + bool get hasValue => _hasValue; + + bool _change(String op, String value) { + _raw = null; + _op = op; + _value = value; + return _hasValue = true; + } + + @override + String compile() { + if (_raw != null) return _raw; + if (_value == null) return null; + var v = sanitizeExpression(_value); + return "$_op '$v'"; + } + + void isEmpty() => equals(''); + + void equals(String value) { + _change('=', value); + } + + void notEquals(String value) { + _change('!=', value); + } + + void like(String value) { + _change('LIKE', value); + } + + @override + void isBetween(String lower, String upper) { + var l = sanitizeExpression(lower), u = sanitizeExpression(upper); + _raw = "BETWEEN '$l' AND '$u'"; + _hasValue = true; + } + + @override + void isNotBetween(String lower, String upper) { + var l = sanitizeExpression(lower), u = sanitizeExpression(upper); + _raw = "NOT BETWEEN '$l' AND '$u'"; + _hasValue = true; + } + + @override + void isIn(Iterable values) { + _raw = 'IN (' + + values.map(sanitizeExpression).map((s) => "'$s'").join(', ') + + ')'; + _hasValue = true; + } + + @override + void isNotIn(Iterable values) { + _raw = 'NOT IN (' + + values.map(sanitizeExpression).map((s) => "'$s'").join(', ') + + ')'; + _hasValue = true; + } +} + +class BooleanSqlExpressionBuilder implements SqlExpressionBuilder { + bool _hasValue = false; + String _op = '=', _raw; + bool _value; + + @override + bool get hasValue => _hasValue; + + bool _change(String op, bool value) { + _raw = null; + _op = op; + _value = value; + return _hasValue = true; + } + + @override + String compile() { + if (_raw != null) return _raw; + if (_value == null) return null; + var v = _value ? 'TRUE' : 'FALSE'; + return '$_op $v'; + } + + void equals(bool value) { + _change('=', value); + } + + void notEquals(bool value) { + _change('!=', value); + } + + @override + void isBetween(bool lower, bool upper) => throw new UnsupportedError( + 'Booleans do not support BETWEEN expressions.'); + + @override + void isNotBetween(bool lower, bool upper) => isBetween(lower, upper); + + @override + void isIn(Iterable values) { + _raw = 'IN (' + values.map((b) => b ? 'TRUE' : 'FALSE').join(', ') + ')'; + _hasValue = true; + } + + @override + void isNotIn(Iterable values) { + _raw = + 'NOT IN (' + values.map((b) => b ? 'TRUE' : 'FALSE').join(', ') + ')'; + _hasValue = true; + } +} + +class DateTimeSqlExpressionBuilder implements SqlExpressionBuilder { + final NumericSqlExpressionBuilder year = + new NumericSqlExpressionBuilder(), + month = new NumericSqlExpressionBuilder(), + day = new NumericSqlExpressionBuilder(), + hour = new NumericSqlExpressionBuilder(), + minute = new NumericSqlExpressionBuilder(), + second = new NumericSqlExpressionBuilder(); + final String columnName; + String _raw; + + DateTimeSqlExpressionBuilder(this.columnName); + + @override + bool get hasValue => + _raw?.isNotEmpty == true || + year.hasValue || + month.hasValue || + day.hasValue || + hour.hasValue || + minute.hasValue || + second.hasValue; + + bool _change(String _op, DateTime dt, bool time) { + var dateString = time ? dateYmdHms.format(dt) : dateYmd.format(dt); + _raw = '$columnName $_op \'$dateString\''; + return true; + } + + operator <(DateTime value) => _change('<', value, true); + + operator <=(DateTime value) => _change('<=', value, true); + + operator >(DateTime value) => _change('>', value, true); + + operator >=(DateTime value) => _change('>=', value, true); + + void equals(DateTime value, {bool includeTime: true}) { + _change('=', value, includeTime != false); + } + + void lessThan(DateTime value, {bool includeTime: true}) { + _change('<', value, includeTime != false); + } + + void lessThanOrEqualTo(DateTime value, {bool includeTime: true}) { + _change('<=', value, includeTime != false); + } + + void greaterThan(DateTime value, {bool includeTime: true}) { + _change('>', value, includeTime != false); + } + + void greaterThanOrEqualTo(DateTime value, {bool includeTime: true}) { + _change('>=', value, includeTime != false); + } + + @override + void isIn(Iterable values) { + _raw = '$columnName IN (' + + values.map(dateYmdHms.format).map((s) => '$s').join(', ') + + ')'; + } + + @override + void isNotIn(Iterable values) { + _raw = '$columnName NOT IN (' + + values.map(dateYmdHms.format).map((s) => '$s').join(', ') + + ')'; + } + + @override + void isBetween(DateTime lower, DateTime upper) { + var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper); + _raw = "$columnName BETWEEN '$l' and '$u'"; + } + + @override + void isNotBetween(DateTime lower, DateTime upper) { + var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper); + _raw = "$columnName NOT BETWEEN '$l' and '$u'"; + } + + @override + String compile() { + if (_raw?.isNotEmpty == true) return _raw; + List parts = []; + if (year.hasValue) parts.add('YEAR($columnName) ${year.compile()}'); + if (month.hasValue) parts.add('MONTH($columnName) ${month.compile()}'); + if (day.hasValue) parts.add('DAY($columnName) ${day.compile()}'); + if (hour.hasValue) parts.add('HOUR($columnName) ${hour.compile()}'); + if (minute.hasValue) parts.add('MINUTE($columnName) ${minute.compile()}'); + if (second.hasValue) parts.add('SECOND($columnName) ${second.compile()}'); + + return parts.isEmpty ? null : parts.join(' AND '); + } +} diff --git a/angel_orm/lib/src/query.dart b/angel_orm/lib/src/query.dart index 406222fc..cfb23413 100644 --- a/angel_orm/lib/src/query.dart +++ b/angel_orm/lib/src/query.dart @@ -1,340 +1,122 @@ -import 'package:intl/intl.dart'; -import 'package:string_scanner/string_scanner.dart'; - -final DateFormat dateYmd = new DateFormat('yyyy-MM-dd'); -final DateFormat dateYmdHms = new DateFormat('yyyy-MM-dd HH:mm:ss'); - -/// Cleans an input SQL expression of common SQL injection points. -String sanitizeExpression(String unsafe) { - var buf = new StringBuffer(); - var scanner = new StringScanner(unsafe); - int ch; - - while (!scanner.isDone) { - // Ignore comment starts - if (scanner.scan('--') || scanner.scan('/*')) - continue; - - // Ignore all single quotes and attempted escape sequences - else if (scanner.scan("'") || scanner.scan('\\')) - continue; - - // Otherwise, add the next char, unless it's a null byte. - else if ((ch = scanner.readChar()) != 0 && ch != null) - buf.writeCharCode(ch); - } - - return buf.toString(); -} - -abstract class SqlExpressionBuilder { - bool get hasValue; +import 'dart:async'; +import 'builder.dart'; +/// A base class for objects that compile to SQL queries, typically within an ORM. +abstract class QueryBase { String compile(); - void isBetween(T lower, T upper); + T deserialize(List row); - void isNotBetween(T lower, T upper); + Future> get(QueryExecutor executor) async { + var sql = compile(); + return executor.query(sql).then((it) => it.map(deserialize).toList()); + } - void isIn(Iterable values); + Future getOne(QueryExecutor executor) { + return get(executor).then((it) => it.isEmpty ? null : it.first); + } - void isNotIn(Iterable values); + Union union(QueryBase other) { + return new Union(this, other); + } + + Union unionAll(QueryBase other) { + return new Union(this, other, all: true); + } } -class NumericSqlExpressionBuilder - implements SqlExpressionBuilder { - bool _hasValue = false; - String _op = '='; - String _raw; - T _value; +/// A SQL `SELECT` query builder. +abstract class Query extends QueryBase { + /// The table against which to execute this query. + String get tableName; - @override - bool get hasValue => _hasValue; + /// The list of fields returned by this query. + /// + /// If it's `null`, then this query will perform a `SELECT *`. + List get fields; - bool _change(String op, T value) { - _raw = null; - _op = op; - _value = value; - return _hasValue = true; - } + /// A reference to an abstract query builder. + /// + /// This is often a generated class. + Where get where; @override String compile() { - if (_raw != null) return _raw; - if (_value == null) return null; - return '$_op $_value'; - } - - operator <(T value) => _change('<', value); - - operator >(T value) => _change('>', value); - - operator <=(T value) => _change('<=', value); - - operator >=(T value) => _change('>=', value); - - void lessThan(T value) { - _change('<', value); - } - - void lessThanOrEqualTo(T value) { - _change('<=', value); - } - - void greaterThan(T value) { - _change('>', value); - } - - void greaterThanOrEqualTo(T value) { - _change('>=', value); - } - - void equals(T value) { - _change('=', value); - } - - void notEquals(T value) { - _change('!=', value); - } - - @override - void isBetween(T lower, T upper) { - _raw = 'BETWEEN $lower AND $upper'; - _hasValue = true; - } - - @override - void isNotBetween(T lower, T upper) { - _raw = 'NOT BETWEEN $lower AND $upper'; - _hasValue = true; - } - - @override - void isIn(Iterable values) { - _raw = 'IN (' + values.join(', ') + ')'; - _hasValue = true; - } - - @override - void isNotIn(Iterable values) { - _raw = 'NOT IN (' + values.join(', ') + ')'; - _hasValue = true; + var b = new StringBuffer('SELECT '); + if (fields == null) + b.write('*'); + else + b.write(fields.join(', ')); + b.write(' FROM $tableName'); + var whereClause = where.compile(); + if (whereClause.isNotEmpty) b.write(' WHERE $whereClause'); + return b.toString(); } } -class StringSqlExpressionBuilder implements SqlExpressionBuilder { - bool _hasValue = false; - String _op = '=', _raw, _value; +/// Builds a SQL `WHERE` clause. +abstract class QueryWhere { + final Set _and = new Set(); + final Set _or = new Set(); + + Map get expressionBuilders; + + void and(QueryWhere other) { + _and.add(other); + } + + void or(QueryWhere other) { + _or.add(other); + } + + String compile() { + var b = new StringBuffer(); + int i = 0; + + for (var entry in expressionBuilders.entries) { + var key = entry.key, builder = entry.value; + if (builder.hasValue) { + if (i++ > 0) b.write(' AND '); + b.write('$key ${builder.compile()}'); + } + } + + for (var other in _and) { + var sql = other.compile(); + if (sql.isNotEmpty) b.write(' AND $sql'); + } + + for (var other in _or) { + var sql = other.compile(); + if (sql.isNotEmpty) b.write(' OR $sql'); + } + + return b.toString(); + } +} + +/// Represents the `UNION` of two subqueries. +class Union extends QueryBase { + final QueryBase left, right; + final bool all; + + Union(this.left, this.right, {this.all: false}); @override - bool get hasValue => _hasValue; - - bool _change(String op, String value) { - _raw = null; - _op = op; - _value = value; - return _hasValue = true; - } + T deserialize(List row) => left.deserialize(row); @override String compile() { - if (_raw != null) return _raw; - if (_value == null) return null; - var v = sanitizeExpression(_value); - return "$_op '$v'"; - } - - void isEmpty() => equals(''); - - void equals(String value) { - _change('=', value); - } - - void notEquals(String value) { - _change('!=', value); - } - - void like(String value) { - _change('LIKE', value); - } - - @override - void isBetween(String lower, String upper) { - var l = sanitizeExpression(lower), u = sanitizeExpression(upper); - _raw = "BETWEEN '$l' AND '$u'"; - _hasValue = true; - } - - @override - void isNotBetween(String lower, String upper) { - var l = sanitizeExpression(lower), u = sanitizeExpression(upper); - _raw = "NOT BETWEEN '$l' AND '$u'"; - _hasValue = true; - } - - @override - void isIn(Iterable values) { - _raw = 'IN (' + - values.map(sanitizeExpression).map((s) => "'$s'").join(', ') + - ')'; - _hasValue = true; - } - - @override - void isNotIn(Iterable values) { - _raw = 'NOT IN (' + - values.map(sanitizeExpression).map((s) => "'$s'").join(', ') + - ')'; - _hasValue = true; + var selector = all == true ? 'UNION ALL' : 'UNION'; + return '(${left.compile()}) $selector (${right.compile()})'; } } -class BooleanSqlExpressionBuilder implements SqlExpressionBuilder { - bool _hasValue = false; - String _op = '=', _raw; - bool _value; +/// An abstract interface that performs queries. +/// +/// This class should be implemented. +abstract class QueryExecutor { + const QueryExecutor(); - @override - bool get hasValue => _hasValue; - - bool _change(String op, bool value) { - _raw = null; - _op = op; - _value = value; - return _hasValue = true; - } - - @override - String compile() { - if (_raw != null) return _raw; - if (_value == null) return null; - var v = _value ? 'TRUE' : 'FALSE'; - return '$_op $v'; - } - - void equals(bool value) { - _change('=', value); - } - - void notEquals(bool value) { - _change('!=', value); - } - - @override - void isBetween(bool lower, bool upper) => throw new UnsupportedError( - 'Booleans do not support BETWEEN expressions.'); - - @override - void isNotBetween(bool lower, bool upper) => isBetween(lower, upper); - - @override - void isIn(Iterable values) { - _raw = 'IN (' + values.map((b) => b ? 'TRUE' : 'FALSE').join(', ') + ')'; - _hasValue = true; - } - - @override - void isNotIn(Iterable values) { - _raw = - 'NOT IN (' + values.map((b) => b ? 'TRUE' : 'FALSE').join(', ') + ')'; - _hasValue = true; - } -} - -class DateTimeSqlExpressionBuilder implements SqlExpressionBuilder { - final NumericSqlExpressionBuilder year = - new NumericSqlExpressionBuilder(), - month = new NumericSqlExpressionBuilder(), - day = new NumericSqlExpressionBuilder(), - hour = new NumericSqlExpressionBuilder(), - minute = new NumericSqlExpressionBuilder(), - second = new NumericSqlExpressionBuilder(); - final String columnName; - String _raw; - - DateTimeSqlExpressionBuilder(this.columnName); - - @override - bool get hasValue => - _raw?.isNotEmpty == true || - year.hasValue || - month.hasValue || - day.hasValue || - hour.hasValue || - minute.hasValue || - second.hasValue; - - bool _change(String _op, DateTime dt, bool time) { - var dateString = time ? dateYmdHms.format(dt) : dateYmd.format(dt); - _raw = '$columnName $_op \'$dateString\''; - return true; - } - - operator <(DateTime value) => _change('<', value, true); - - operator <=(DateTime value) => _change('<=', value, true); - - operator >(DateTime value) => _change('>', value, true); - - operator >=(DateTime value) => _change('>=', value, true); - - void equals(DateTime value, {bool includeTime: true}) { - _change('=', value, includeTime != false); - } - - void lessThan(DateTime value, {bool includeTime: true}) { - _change('<', value, includeTime != false); - } - - void lessThanOrEqualTo(DateTime value, {bool includeTime: true}) { - _change('<=', value, includeTime != false); - } - - void greaterThan(DateTime value, {bool includeTime: true}) { - _change('>', value, includeTime != false); - } - - void greaterThanOrEqualTo(DateTime value, {bool includeTime: true}) { - _change('>=', value, includeTime != false); - } - - @override - void isIn(Iterable values) { - _raw = '$columnName IN (' + - values.map(dateYmdHms.format).map((s) => '$s').join(', ') + - ')'; - } - - @override - void isNotIn(Iterable values) { - _raw = '$columnName NOT IN (' + - values.map(dateYmdHms.format).map((s) => '$s').join(', ') + - ')'; - } - - @override - void isBetween(DateTime lower, DateTime upper) { - var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper); - _raw = "$columnName BETWEEN '$l' and '$u'"; - } - - @override - void isNotBetween(DateTime lower, DateTime upper) { - var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper); - _raw = "$columnName NOT BETWEEN '$l' and '$u'"; - } - - @override - String compile() { - if (_raw?.isNotEmpty == true) return _raw; - List parts = []; - if (year.hasValue) parts.add('YEAR($columnName) ${year.compile()}'); - if (month.hasValue) parts.add('MONTH($columnName) ${month.compile()}'); - if (day.hasValue) parts.add('DAY($columnName) ${day.compile()}'); - if (hour.hasValue) parts.add('HOUR($columnName) ${hour.compile()}'); - if (minute.hasValue) parts.add('MINUTE($columnName) ${minute.compile()}'); - if (second.hasValue) parts.add('SECOND($columnName) ${second.compile()}'); - - return parts.isEmpty ? null : parts.join(' AND '); - } + Future> query(String query); } diff --git a/angel_orm/pubspec.yaml b/angel_orm/pubspec.yaml index 5217710e..532c1230 100644 --- a/angel_orm/pubspec.yaml +++ b/angel_orm/pubspec.yaml @@ -10,4 +10,7 @@ dependencies: meta: ^1.0.0 string_scanner: ^1.0.0 dev_dependencies: - angel_model: ^1.0.0 \ No newline at end of file + angel_model: ^1.0.0 + angel_serialize: ^2.0.0 + angel_serialize_generator: ^2.0.0 + build_runner: ^1.0.0 \ No newline at end of file