platform/angel_orm/lib/src/query.dart

333 lines
8.4 KiB
Dart
Raw Normal View History

2017-07-14 22:04:58 +00:00
import 'package:meta/meta.dart';
2017-06-18 04:19:05 +00:00
import 'package:intl/intl.dart';
2017-07-14 22:04:58 +00:00
import 'package:string_scanner/string_scanner.dart';
2017-06-18 04:19:05 +00:00
2017-06-24 21:21:32 +00:00
final DateFormat DATE_YMD = new DateFormat('yyyy-MM-dd');
final DateFormat DATE_YMD_HMS = new DateFormat('yyyy-MM-dd HH:mm:ss');
2017-07-14 22:04:58 +00:00
/// 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.
2017-12-07 08:21:49 +00:00
else if ((ch = scanner.readChar()) != 0 && ch != null)
buf.writeCharCode(ch);
2017-07-14 22:04:58 +00:00
}
return buf.toString();
}
2017-06-18 04:19:05 +00:00
abstract class SqlExpressionBuilder {
bool get hasValue;
String compile();
2017-07-14 22:04:58 +00:00
void isBetween(lower, upper);
void isNotBetween(lower, upper);
void isIn(Iterable values);
void isNotIn(Iterable values);
2017-06-18 04:19:05 +00:00
}
class NumericSqlExpressionBuilder<T extends num>
implements SqlExpressionBuilder {
bool _hasValue = false;
String _op = '=';
2017-07-14 22:04:58 +00:00
String _raw;
2017-06-18 04:19:05 +00:00
T _value;
@override
bool get hasValue => _hasValue;
bool _change(String op, T value) {
2017-07-14 22:04:58 +00:00
_raw = null;
2017-06-18 04:19:05 +00:00
_op = op;
_value = value;
return _hasValue = true;
}
@override
String compile() {
2017-07-14 22:04:58 +00:00
if (_raw != null) return _raw;
2017-06-18 04:19:05 +00:00
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);
}
2017-07-14 22:04:58 +00:00
@override
void isBetween(@checked T lower, @checked T upper) {
_raw = 'BETWEEN $lower AND $upper';
_hasValue = true;
}
@override
void isNotBetween(@checked T lower, @checked T upper) {
_raw = 'NOT BETWEEN $lower AND $upper';
_hasValue = true;
}
@override
void isIn(@checked Iterable<T> values) {
_raw = 'IN (' + values.join(', ') + ')';
_hasValue = true;
}
@override
void isNotIn(@checked Iterable<T> values) {
_raw = 'NOT IN (' + values.join(', ') + ')';
_hasValue = true;
}
2017-06-18 04:19:05 +00:00
}
class StringSqlExpressionBuilder implements SqlExpressionBuilder {
bool _hasValue = false;
2017-07-14 22:04:58 +00:00
String _op = '=', _raw, _value;
2017-06-18 04:19:05 +00:00
@override
bool get hasValue => _hasValue;
bool _change(String op, String value) {
2017-07-14 22:04:58 +00:00
_raw = null;
2017-06-18 04:19:05 +00:00
_op = op;
_value = value;
return _hasValue = true;
}
@override
String compile() {
2017-07-14 22:04:58 +00:00
if (_raw != null) return _raw;
2017-06-18 04:19:05 +00:00
if (_value == null) return null;
2017-07-14 22:04:58 +00:00
var v = sanitizeExpression(_value);
2017-07-09 16:53:35 +00:00
return "$_op '$v'";
2017-06-18 04:19:05 +00:00
}
void isEmpty() => equals('');
void equals(String value) {
_change('=', value);
}
void notEquals(String value) {
_change('!=', value);
}
void like(String value) {
_change('LIKE', value);
}
2017-07-14 22:04:58 +00:00
@override
void isBetween(@checked String lower, @checked String upper) {
var l = sanitizeExpression(lower), u = sanitizeExpression(upper);
_raw = "BETWEEN '$l' AND '$u'";
_hasValue = true;
}
@override
void isNotBetween(@checked String lower, @checked String upper) {
var l = sanitizeExpression(lower), u = sanitizeExpression(upper);
_raw = "NOT BETWEEN '$l' AND '$u'";
_hasValue = true;
}
@override
void isIn(@checked Iterable<String> values) {
_raw = 'IN (' +
values.map(sanitizeExpression).map((s) => "'$s'").join(', ') +
')';
_hasValue = true;
}
@override
void isNotIn(@checked Iterable<String> values) {
_raw = 'NOT IN (' +
values.map(sanitizeExpression).map((s) => "'$s'").join(', ') +
')';
_hasValue = true;
}
2017-06-18 04:19:05 +00:00
}
class BooleanSqlExpressionBuilder implements SqlExpressionBuilder {
bool _hasValue = false;
2017-07-14 22:04:58 +00:00
String _op = '=', _raw;
2017-06-18 04:19:05 +00:00
bool _value;
@override
bool get hasValue => _hasValue;
bool _change(String op, bool value) {
2017-07-14 22:04:58 +00:00
_raw = null;
2017-06-18 04:19:05 +00:00
_op = op;
_value = value;
return _hasValue = true;
}
@override
String compile() {
2017-07-14 22:04:58 +00:00
if (_raw != null) return _raw;
2017-06-18 04:19:05 +00:00
if (_value == null) return null;
2017-07-09 16:53:35 +00:00
var v = _value ? 'TRUE' : 'FALSE';
2017-06-18 04:19:05 +00:00
return '$_op $v';
}
void equals(bool value) {
_change('=', value);
}
void notEquals(bool value) {
_change('!=', value);
}
2017-07-14 22:04:58 +00:00
@override
void isBetween(@checked bool lower, @checked bool upper) =>
throw new UnsupportedError(
'Booleans do not support BETWEEN expressions.');
@override
void isNotBetween(@checked bool lower, @checked bool upper) =>
isBetween(lower, upper);
@override
void isIn(@checked Iterable<bool> values) {
_raw = 'IN (' + values.map((b) => b ? 'TRUE' : 'FALSE').join(', ') + ')';
_hasValue = true;
}
@override
void isNotIn(@checked Iterable<bool> values) {
_raw =
'NOT IN (' + values.map((b) => b ? 'TRUE' : 'FALSE').join(', ') + ')';
_hasValue = true;
}
2017-06-18 04:19:05 +00:00
}
class DateTimeSqlExpressionBuilder implements SqlExpressionBuilder {
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) {
2017-06-24 21:21:32 +00:00
var dateString = time ? DATE_YMD_HMS.format(dt) : DATE_YMD.format(dt);
2017-07-15 15:11:57 +00:00
_raw = '$columnName $_op \'$dateString\'';
2017-06-18 04:19:05 +00:00
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);
}
2017-07-14 22:04:58 +00:00
@override
void isIn(@checked Iterable<DateTime> values) {
2017-07-15 15:11:57 +00:00
_raw = '$columnName IN (' +
values.map(DATE_YMD_HMS.format).map((s) => '$s').join(', ') +
2017-07-14 22:04:58 +00:00
')';
}
@override
void isNotIn(@checked Iterable<DateTime> values) {
2017-07-15 15:11:57 +00:00
_raw = '$columnName NOT IN (' +
values.map(DATE_YMD_HMS.format).map((s) => '$s').join(', ') +
2017-07-14 22:04:58 +00:00
')';
}
@override
void isBetween(@checked DateTime lower, @checked DateTime upper) {
var l = DATE_YMD_HMS.format(lower), u = DATE_YMD_HMS.format(upper);
2017-07-15 15:11:57 +00:00
_raw = "$columnName BETWEEN '$l' and '$u'";
2017-07-14 22:04:58 +00:00
}
@override
void isNotBetween(@checked DateTime lower, @checked DateTime upper) {
var l = DATE_YMD_HMS.format(lower), u = DATE_YMD_HMS.format(upper);
2017-07-15 15:11:57 +00:00
_raw = "$columnName NOT BETWEEN '$l' and '$u'";
2017-07-14 22:04:58 +00:00
}
2017-06-18 04:19:05 +00:00
@override
String compile() {
if (_raw?.isNotEmpty == true) return _raw;
List<String> parts = [];
2017-07-15 15:11:57 +00:00
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()}');
2017-06-18 04:19:05 +00:00
return parts.isEmpty ? null : parts.join(' AND ');
}
}