bring back older query builder
This commit is contained in:
parent
007ea0b5e0
commit
cb73f4a112
9 changed files with 721 additions and 342 deletions
67
angel_orm/example/main.angel_serialize.g.part
Normal file
67
angel_orm/example/main.angel_serialize.g.part
Normal file
|
@ -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<String, dynamic> toJson() {
|
||||||
|
return EmployeeSerializer.toMap(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +1,92 @@
|
||||||
import 'package:angel_model/angel_model.dart';
|
import 'package:angel_model/angel_model.dart';
|
||||||
import 'package:angel_orm/angel_orm.dart';
|
import 'package:angel_orm/angel_orm.dart';
|
||||||
|
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
|
class _FakeExecutor extends QueryExecutor {
|
||||||
abstract class Company extends Model {
|
const _FakeExecutor();
|
||||||
String get name;
|
|
||||||
|
|
||||||
bool get isFortune500;
|
@override
|
||||||
|
Future<List<List>> 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 {
|
abstract class _Employee extends Model {
|
||||||
@belongsTo
|
|
||||||
Company get company;
|
|
||||||
|
|
||||||
String get firstName;
|
String get firstName;
|
||||||
|
|
||||||
String get lastName;
|
String get lastName;
|
||||||
|
|
||||||
double get salary;
|
double get salary;
|
||||||
|
}
|
||||||
bool get isFortune500Employee => company.isFortune500;
|
|
||||||
|
class EmployeeQuery extends Query<Employee, EmployeeQueryWhere> {
|
||||||
|
@override
|
||||||
|
final EmployeeQueryWhere where = new EmployeeQueryWhere();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tableName => 'employees';
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> 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<String, SqlExpressionBuilder> get expressionBuilders {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'first_name': firstName,
|
||||||
|
'last_name': lastName,
|
||||||
|
'salary': salary,
|
||||||
|
'created_at': createdAt,
|
||||||
|
'updated_at': updatedAt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
final NumericSqlExpressionBuilder<int> id =
|
||||||
|
new NumericSqlExpressionBuilder<int>();
|
||||||
|
|
||||||
|
final StringSqlExpressionBuilder firstName = new StringSqlExpressionBuilder();
|
||||||
|
|
||||||
|
final StringSqlExpressionBuilder lastName = new StringSqlExpressionBuilder();
|
||||||
|
|
||||||
|
final NumericSqlExpressionBuilder<double> salary =
|
||||||
|
new NumericSqlExpressionBuilder<double>();
|
||||||
|
|
||||||
|
final DateTimeSqlExpressionBuilder createdAt =
|
||||||
|
new DateTimeSqlExpressionBuilder('created_at');
|
||||||
|
|
||||||
|
final DateTimeSqlExpressionBuilder updatedAt =
|
||||||
|
new DateTimeSqlExpressionBuilder('updated_at');
|
||||||
}
|
}
|
||||||
|
|
71
angel_orm/example/main.g.dart
Normal file
71
angel_orm/example/main.g.dart
Normal file
|
@ -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<String, dynamic> toJson() {
|
||||||
|
return EmployeeSerializer.toMap(this);
|
||||||
|
}
|
||||||
|
}
|
64
angel_orm/example/main.serializer.g.dart
Normal file
64
angel_orm/example/main.serializer.g.dart
Normal file
|
@ -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<String, dynamic> 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<String> allFields = const <String>[
|
||||||
|
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';
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
export 'src/annotations.dart';
|
export 'src/annotations.dart';
|
||||||
|
export 'src/builder.dart';
|
||||||
export 'src/migration.dart';
|
export 'src/migration.dart';
|
||||||
export 'src/relations.dart';
|
export 'src/relations.dart';
|
||||||
export 'src/query.dart';
|
export 'src/query.dart';
|
|
@ -1,23 +1,9 @@
|
||||||
const Orm mongoDBOrm = const Orm(OrmType.mongoDB);
|
const Orm orm = const Orm();
|
||||||
|
|
||||||
const Orm rethinkDBOrm = const Orm(OrmType.rethinkDB);
|
|
||||||
|
|
||||||
const Orm postgreSqlOrm = const Orm(OrmType.postgreSql);
|
|
||||||
|
|
||||||
const Orm mySqlOrm = const Orm(OrmType.mySql);
|
|
||||||
|
|
||||||
class Orm {
|
class Orm {
|
||||||
final OrmType type;
|
|
||||||
final String tableName;
|
final String tableName;
|
||||||
|
|
||||||
const Orm(this.type, {this.tableName});
|
const Orm({this.tableName});
|
||||||
}
|
|
||||||
|
|
||||||
enum OrmType {
|
|
||||||
mongoDB,
|
|
||||||
rethinkDB,
|
|
||||||
mySql,
|
|
||||||
postgreSql,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CanJoin {
|
class CanJoin {
|
||||||
|
|
340
angel_orm/lib/src/builder.dart
Normal file
340
angel_orm/lib/src/builder.dart
Normal file
|
@ -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<T> {
|
||||||
|
bool get hasValue;
|
||||||
|
|
||||||
|
String compile();
|
||||||
|
|
||||||
|
void isBetween(T lower, T upper);
|
||||||
|
|
||||||
|
void isNotBetween(T lower, T upper);
|
||||||
|
|
||||||
|
void isIn(Iterable<T> values);
|
||||||
|
|
||||||
|
void isNotIn(Iterable<T> values);
|
||||||
|
}
|
||||||
|
|
||||||
|
class NumericSqlExpressionBuilder<T extends num>
|
||||||
|
implements SqlExpressionBuilder<T> {
|
||||||
|
bool _hasValue = false;
|
||||||
|
String _op = '=';
|
||||||
|
String _raw;
|
||||||
|
T _value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get hasValue => _hasValue;
|
||||||
|
|
||||||
|
bool _change(String op, T value) {
|
||||||
|
_raw = null;
|
||||||
|
_op = op;
|
||||||
|
_value = value;
|
||||||
|
return _hasValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String compile() {
|
||||||
|
if (_raw != null) return _raw;
|
||||||
|
if (_value == null) return null;
|
||||||
|
return '$_op $_value';
|
||||||
|
}
|
||||||
|
|
||||||
|
operator <(T value) => _change('<', value);
|
||||||
|
|
||||||
|
operator >(T value) => _change('>', value);
|
||||||
|
|
||||||
|
operator <=(T value) => _change('<=', value);
|
||||||
|
|
||||||
|
operator >=(T value) => _change('>=', value);
|
||||||
|
|
||||||
|
void lessThan(T value) {
|
||||||
|
_change('<', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lessThanOrEqualTo(T value) {
|
||||||
|
_change('<=', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void greaterThan(T value) {
|
||||||
|
_change('>', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void greaterThanOrEqualTo(T value) {
|
||||||
|
_change('>=', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void equals(T value) {
|
||||||
|
_change('=', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void notEquals(T value) {
|
||||||
|
_change('!=', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void isBetween(T lower, T upper) {
|
||||||
|
_raw = 'BETWEEN $lower AND $upper';
|
||||||
|
_hasValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void isNotBetween(T lower, T upper) {
|
||||||
|
_raw = 'NOT BETWEEN $lower AND $upper';
|
||||||
|
_hasValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void isIn(Iterable<T> values) {
|
||||||
|
_raw = 'IN (' + values.join(', ') + ')';
|
||||||
|
_hasValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void isNotIn(Iterable<T> values) {
|
||||||
|
_raw = 'NOT IN (' + values.join(', ') + ')';
|
||||||
|
_hasValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StringSqlExpressionBuilder implements SqlExpressionBuilder<String> {
|
||||||
|
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<String> values) {
|
||||||
|
_raw = 'IN (' +
|
||||||
|
values.map(sanitizeExpression).map((s) => "'$s'").join(', ') +
|
||||||
|
')';
|
||||||
|
_hasValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void isNotIn(Iterable<String> values) {
|
||||||
|
_raw = 'NOT IN (' +
|
||||||
|
values.map(sanitizeExpression).map((s) => "'$s'").join(', ') +
|
||||||
|
')';
|
||||||
|
_hasValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BooleanSqlExpressionBuilder implements SqlExpressionBuilder<bool> {
|
||||||
|
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<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 implements SqlExpressionBuilder<DateTime> {
|
||||||
|
final NumericSqlExpressionBuilder<int> year =
|
||||||
|
new NumericSqlExpressionBuilder<int>(),
|
||||||
|
month = new NumericSqlExpressionBuilder<int>(),
|
||||||
|
day = new NumericSqlExpressionBuilder<int>(),
|
||||||
|
hour = new NumericSqlExpressionBuilder<int>(),
|
||||||
|
minute = new NumericSqlExpressionBuilder<int>(),
|
||||||
|
second = new NumericSqlExpressionBuilder<int>();
|
||||||
|
final String columnName;
|
||||||
|
String _raw;
|
||||||
|
|
||||||
|
DateTimeSqlExpressionBuilder(this.columnName);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get hasValue =>
|
||||||
|
_raw?.isNotEmpty == true ||
|
||||||
|
year.hasValue ||
|
||||||
|
month.hasValue ||
|
||||||
|
day.hasValue ||
|
||||||
|
hour.hasValue ||
|
||||||
|
minute.hasValue ||
|
||||||
|
second.hasValue;
|
||||||
|
|
||||||
|
bool _change(String _op, DateTime dt, bool time) {
|
||||||
|
var dateString = time ? dateYmdHms.format(dt) : dateYmd.format(dt);
|
||||||
|
_raw = '$columnName $_op \'$dateString\'';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator <(DateTime value) => _change('<', value, true);
|
||||||
|
|
||||||
|
operator <=(DateTime value) => _change('<=', value, true);
|
||||||
|
|
||||||
|
operator >(DateTime value) => _change('>', value, true);
|
||||||
|
|
||||||
|
operator >=(DateTime value) => _change('>=', value, true);
|
||||||
|
|
||||||
|
void equals(DateTime value, {bool includeTime: true}) {
|
||||||
|
_change('=', value, includeTime != false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lessThan(DateTime value, {bool includeTime: true}) {
|
||||||
|
_change('<', value, includeTime != false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lessThanOrEqualTo(DateTime value, {bool includeTime: true}) {
|
||||||
|
_change('<=', value, includeTime != false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void greaterThan(DateTime value, {bool includeTime: true}) {
|
||||||
|
_change('>', value, includeTime != false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void greaterThanOrEqualTo(DateTime value, {bool includeTime: true}) {
|
||||||
|
_change('>=', value, includeTime != false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void isIn(Iterable<DateTime> values) {
|
||||||
|
_raw = '$columnName IN (' +
|
||||||
|
values.map(dateYmdHms.format).map((s) => '$s').join(', ') +
|
||||||
|
')';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void isNotIn(Iterable<DateTime> values) {
|
||||||
|
_raw = '$columnName NOT IN (' +
|
||||||
|
values.map(dateYmdHms.format).map((s) => '$s').join(', ') +
|
||||||
|
')';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void isBetween(DateTime lower, DateTime upper) {
|
||||||
|
var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper);
|
||||||
|
_raw = "$columnName BETWEEN '$l' and '$u'";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void isNotBetween(DateTime lower, DateTime upper) {
|
||||||
|
var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper);
|
||||||
|
_raw = "$columnName NOT BETWEEN '$l' and '$u'";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String compile() {
|
||||||
|
if (_raw?.isNotEmpty == true) return _raw;
|
||||||
|
List<String> parts = [];
|
||||||
|
if (year.hasValue) parts.add('YEAR($columnName) ${year.compile()}');
|
||||||
|
if (month.hasValue) parts.add('MONTH($columnName) ${month.compile()}');
|
||||||
|
if (day.hasValue) parts.add('DAY($columnName) ${day.compile()}');
|
||||||
|
if (hour.hasValue) parts.add('HOUR($columnName) ${hour.compile()}');
|
||||||
|
if (minute.hasValue) parts.add('MINUTE($columnName) ${minute.compile()}');
|
||||||
|
if (second.hasValue) parts.add('SECOND($columnName) ${second.compile()}');
|
||||||
|
|
||||||
|
return parts.isEmpty ? null : parts.join(' AND ');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,340 +1,122 @@
|
||||||
import 'package:intl/intl.dart';
|
import 'dart:async';
|
||||||
import 'package:string_scanner/string_scanner.dart';
|
import 'builder.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<T> {
|
|
||||||
bool get hasValue;
|
|
||||||
|
|
||||||
|
/// A base class for objects that compile to SQL queries, typically within an ORM.
|
||||||
|
abstract class QueryBase<T> {
|
||||||
String compile();
|
String compile();
|
||||||
|
|
||||||
void isBetween(T lower, T upper);
|
T deserialize(List row);
|
||||||
|
|
||||||
void isNotBetween(T lower, T upper);
|
Future<List<T>> get(QueryExecutor executor) async {
|
||||||
|
var sql = compile();
|
||||||
void isIn(Iterable<T> values);
|
return executor.query(sql).then((it) => it.map(deserialize).toList());
|
||||||
|
|
||||||
void isNotIn(Iterable<T> values);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class NumericSqlExpressionBuilder<T extends num>
|
Future<T> getOne(QueryExecutor executor) {
|
||||||
implements SqlExpressionBuilder<T> {
|
return get(executor).then((it) => it.isEmpty ? null : it.first);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Union<T> union(QueryBase<T> other) {
|
||||||
|
return new Union(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
Union<T> unionAll(QueryBase<T> other) {
|
||||||
|
return new Union(this, other, all: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A SQL `SELECT` query builder.
|
||||||
|
abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
||||||
|
/// The table against which to execute this query.
|
||||||
|
String get tableName;
|
||||||
|
|
||||||
|
/// The list of fields returned by this query.
|
||||||
|
///
|
||||||
|
/// If it's `null`, then this query will perform a `SELECT *`.
|
||||||
|
List<String> get fields;
|
||||||
|
|
||||||
|
/// A reference to an abstract query builder.
|
||||||
|
///
|
||||||
|
/// This is often a generated class.
|
||||||
|
Where get where;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String compile() {
|
String compile() {
|
||||||
if (_raw != null) return _raw;
|
var b = new StringBuffer('SELECT ');
|
||||||
if (_value == null) return null;
|
if (fields == null)
|
||||||
return '$_op $_value';
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operator <(T value) => _change('<', value);
|
/// Builds a SQL `WHERE` clause.
|
||||||
|
abstract class QueryWhere {
|
||||||
|
final Set<QueryWhere> _and = new Set();
|
||||||
|
final Set<QueryWhere> _or = new Set();
|
||||||
|
|
||||||
operator >(T value) => _change('>', value);
|
Map<String, SqlExpressionBuilder> get expressionBuilders;
|
||||||
|
|
||||||
operator <=(T value) => _change('<=', value);
|
void and(QueryWhere other) {
|
||||||
|
_and.add(other);
|
||||||
operator >=(T value) => _change('>=', value);
|
|
||||||
|
|
||||||
void lessThan(T value) {
|
|
||||||
_change('<', value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void lessThanOrEqualTo(T value) {
|
void or(QueryWhere other) {
|
||||||
_change('<=', value);
|
_or.add(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
void greaterThan(T value) {
|
String compile() {
|
||||||
_change('>', value);
|
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()}');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void greaterThanOrEqualTo(T value) {
|
for (var other in _and) {
|
||||||
_change('>=', value);
|
var sql = other.compile();
|
||||||
|
if (sql.isNotEmpty) b.write(' AND $sql');
|
||||||
}
|
}
|
||||||
|
|
||||||
void equals(T value) {
|
for (var other in _or) {
|
||||||
_change('=', value);
|
var sql = other.compile();
|
||||||
|
if (sql.isNotEmpty) b.write(' OR $sql');
|
||||||
}
|
}
|
||||||
|
|
||||||
void notEquals(T value) {
|
return b.toString();
|
||||||
_change('!=', value);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the `UNION` of two subqueries.
|
||||||
|
class Union<T> extends QueryBase<T> {
|
||||||
|
final QueryBase<T> left, right;
|
||||||
|
final bool all;
|
||||||
|
|
||||||
|
Union(this.left, this.right, {this.all: false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void isBetween(T lower, T upper) {
|
T deserialize(List row) => left.deserialize(row);
|
||||||
_raw = 'BETWEEN $lower AND $upper';
|
|
||||||
_hasValue = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void isNotBetween(T lower, T upper) {
|
|
||||||
_raw = 'NOT BETWEEN $lower AND $upper';
|
|
||||||
_hasValue = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void isIn(Iterable<T> values) {
|
|
||||||
_raw = 'IN (' + values.join(', ') + ')';
|
|
||||||
_hasValue = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void isNotIn(Iterable<T> values) {
|
|
||||||
_raw = 'NOT IN (' + values.join(', ') + ')';
|
|
||||||
_hasValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class StringSqlExpressionBuilder implements SqlExpressionBuilder<String> {
|
|
||||||
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
|
@override
|
||||||
String compile() {
|
String compile() {
|
||||||
if (_raw != null) return _raw;
|
var selector = all == true ? 'UNION ALL' : 'UNION';
|
||||||
if (_value == null) return null;
|
return '(${left.compile()}) $selector (${right.compile()})';
|
||||||
var v = sanitizeExpression(_value);
|
|
||||||
return "$_op '$v'";
|
|
||||||
}
|
|
||||||
|
|
||||||
void isEmpty() => equals('');
|
|
||||||
|
|
||||||
void equals(String value) {
|
|
||||||
_change('=', value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void notEquals(String value) {
|
|
||||||
_change('!=', value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void like(String value) {
|
|
||||||
_change('LIKE', value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void isBetween(String lower, String upper) {
|
|
||||||
var l = sanitizeExpression(lower), u = sanitizeExpression(upper);
|
|
||||||
_raw = "BETWEEN '$l' AND '$u'";
|
|
||||||
_hasValue = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void isNotBetween(String lower, String upper) {
|
|
||||||
var l = sanitizeExpression(lower), u = sanitizeExpression(upper);
|
|
||||||
_raw = "NOT BETWEEN '$l' AND '$u'";
|
|
||||||
_hasValue = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void isIn(Iterable<String> values) {
|
|
||||||
_raw = 'IN (' +
|
|
||||||
values.map(sanitizeExpression).map((s) => "'$s'").join(', ') +
|
|
||||||
')';
|
|
||||||
_hasValue = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void isNotIn(Iterable<String> values) {
|
|
||||||
_raw = 'NOT IN (' +
|
|
||||||
values.map(sanitizeExpression).map((s) => "'$s'").join(', ') +
|
|
||||||
')';
|
|
||||||
_hasValue = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BooleanSqlExpressionBuilder implements SqlExpressionBuilder<bool> {
|
/// An abstract interface that performs queries.
|
||||||
bool _hasValue = false;
|
///
|
||||||
String _op = '=', _raw;
|
/// This class should be implemented.
|
||||||
bool _value;
|
abstract class QueryExecutor {
|
||||||
|
const QueryExecutor();
|
||||||
|
|
||||||
@override
|
Future<List<List>> query(String query);
|
||||||
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<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 implements SqlExpressionBuilder<DateTime> {
|
|
||||||
final NumericSqlExpressionBuilder<int> year =
|
|
||||||
new NumericSqlExpressionBuilder<int>(),
|
|
||||||
month = new NumericSqlExpressionBuilder<int>(),
|
|
||||||
day = new NumericSqlExpressionBuilder<int>(),
|
|
||||||
hour = new NumericSqlExpressionBuilder<int>(),
|
|
||||||
minute = new NumericSqlExpressionBuilder<int>(),
|
|
||||||
second = new NumericSqlExpressionBuilder<int>();
|
|
||||||
final String columnName;
|
|
||||||
String _raw;
|
|
||||||
|
|
||||||
DateTimeSqlExpressionBuilder(this.columnName);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get hasValue =>
|
|
||||||
_raw?.isNotEmpty == true ||
|
|
||||||
year.hasValue ||
|
|
||||||
month.hasValue ||
|
|
||||||
day.hasValue ||
|
|
||||||
hour.hasValue ||
|
|
||||||
minute.hasValue ||
|
|
||||||
second.hasValue;
|
|
||||||
|
|
||||||
bool _change(String _op, DateTime dt, bool time) {
|
|
||||||
var dateString = time ? dateYmdHms.format(dt) : dateYmd.format(dt);
|
|
||||||
_raw = '$columnName $_op \'$dateString\'';
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator <(DateTime value) => _change('<', value, true);
|
|
||||||
|
|
||||||
operator <=(DateTime value) => _change('<=', value, true);
|
|
||||||
|
|
||||||
operator >(DateTime value) => _change('>', value, true);
|
|
||||||
|
|
||||||
operator >=(DateTime value) => _change('>=', value, true);
|
|
||||||
|
|
||||||
void equals(DateTime value, {bool includeTime: true}) {
|
|
||||||
_change('=', value, includeTime != false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void lessThan(DateTime value, {bool includeTime: true}) {
|
|
||||||
_change('<', value, includeTime != false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void lessThanOrEqualTo(DateTime value, {bool includeTime: true}) {
|
|
||||||
_change('<=', value, includeTime != false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void greaterThan(DateTime value, {bool includeTime: true}) {
|
|
||||||
_change('>', value, includeTime != false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void greaterThanOrEqualTo(DateTime value, {bool includeTime: true}) {
|
|
||||||
_change('>=', value, includeTime != false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void isIn(Iterable<DateTime> values) {
|
|
||||||
_raw = '$columnName IN (' +
|
|
||||||
values.map(dateYmdHms.format).map((s) => '$s').join(', ') +
|
|
||||||
')';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void isNotIn(Iterable<DateTime> values) {
|
|
||||||
_raw = '$columnName NOT IN (' +
|
|
||||||
values.map(dateYmdHms.format).map((s) => '$s').join(', ') +
|
|
||||||
')';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void isBetween(DateTime lower, DateTime upper) {
|
|
||||||
var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper);
|
|
||||||
_raw = "$columnName BETWEEN '$l' and '$u'";
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void isNotBetween(DateTime lower, DateTime upper) {
|
|
||||||
var l = dateYmdHms.format(lower), u = dateYmdHms.format(upper);
|
|
||||||
_raw = "$columnName NOT BETWEEN '$l' and '$u'";
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String compile() {
|
|
||||||
if (_raw?.isNotEmpty == true) return _raw;
|
|
||||||
List<String> parts = [];
|
|
||||||
if (year.hasValue) parts.add('YEAR($columnName) ${year.compile()}');
|
|
||||||
if (month.hasValue) parts.add('MONTH($columnName) ${month.compile()}');
|
|
||||||
if (day.hasValue) parts.add('DAY($columnName) ${day.compile()}');
|
|
||||||
if (hour.hasValue) parts.add('HOUR($columnName) ${hour.compile()}');
|
|
||||||
if (minute.hasValue) parts.add('MINUTE($columnName) ${minute.compile()}');
|
|
||||||
if (second.hasValue) parts.add('SECOND($columnName) ${second.compile()}');
|
|
||||||
|
|
||||||
return parts.isEmpty ? null : parts.join(' AND ');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,3 +11,6 @@ dependencies:
|
||||||
string_scanner: ^1.0.0
|
string_scanner: ^1.0.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angel_model: ^1.0.0
|
angel_model: ^1.0.0
|
||||||
|
angel_serialize: ^2.0.0
|
||||||
|
angel_serialize_generator: ^2.0.0
|
||||||
|
build_runner: ^1.0.0
|
Loading…
Reference in a new issue