1.0.0-alpha+2

This commit is contained in:
thosakwe 2017-07-14 18:04:58 -04:00
parent 2a3040c9aa
commit 18946b4320
4 changed files with 170 additions and 10 deletions

4
angel_orm/CHANGELOG.md Normal file
View file

@ -0,0 +1,4 @@
# 1.0.0-alpha+2
* Added `isIn`, `isNotIn`, `isBetween`, `isNotBetween` to `SqlExpressionBuilder` and its
subclasses.
* Added a dependency on `package:meta`.

View file

@ -7,6 +7,9 @@ typedef FutureOr<PostgreSQLConnection> PostgreSQLConnector();
/// Pools connections to a PostgreSQL database. /// Pools connections to a PostgreSQL database.
class PostgreSQLConnectionPool { class PostgreSQLConnectionPool {
final List<PostgreSQLConnection> _connections = [];
final List<int> _opened = [];
int _index = 0;
Pool _pool; Pool _pool;
/// The maximum number of concurrent connections to the database. /// The maximum number of concurrent connections to the database.
@ -26,11 +29,22 @@ class PostgreSQLConnectionPool {
} }
Future<PostgreSQLConnection> _connect() async { Future<PostgreSQLConnection> _connect() async {
var connection = await connector() as PostgreSQLConnection; if (_connections.isEmpty) {
await connection.open(); for (int i = 0; i < concurrency; i++) {
_connections.add(await connector());
}
}
var connection = _connections[_index++];
if (_index >= _connections.length) _index = 0;
if (!_opened.contains(connection.hashCode)) await connection.open();
return connection; return connection;
} }
Future close() => Future.wait(_connections.map((c) => c.close()));
/// Connects to the database, and then executes the [callback]. /// Connects to the database, and then executes the [callback].
/// ///
/// Returns the result of [callback]. /// Returns the result of [callback].
@ -39,7 +53,11 @@ class PostgreSQLConnectionPool {
return _connect().then((connection) { return _connect().then((connection) {
return new Future<T>.sync(() => callback(connection)) return new Future<T>.sync(() => callback(connection))
.whenComplete(() async { .whenComplete(() async {
if (!connection.isClosed) await connection.close(); if (connection.isClosed) {
_connections
..remove(connection)
..add(await connector());
}
resx.release(); resx.release();
}); });
}); });

View file

@ -1,23 +1,54 @@
import 'package:charcode/charcode.dart';
import 'package:meta/meta.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:string_scanner/string_scanner.dart';
final DateFormat DATE_YMD = new DateFormat('yyyy-MM-dd'); final DateFormat DATE_YMD = new DateFormat('yyyy-MM-dd');
final DateFormat DATE_YMD_HMS = new DateFormat('yyyy-MM-dd HH:mm:ss'); final DateFormat DATE_YMD_HMS = 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) buf.writeCharCode(ch);
}
return buf.toString();
}
abstract class SqlExpressionBuilder { abstract class SqlExpressionBuilder {
bool get hasValue; bool get hasValue;
String compile(); String compile();
void isBetween(lower, upper);
void isNotBetween(lower, upper);
void isIn(Iterable values);
void isNotIn(Iterable values);
} }
class NumericSqlExpressionBuilder<T extends num> class NumericSqlExpressionBuilder<T extends num>
implements SqlExpressionBuilder { implements SqlExpressionBuilder {
bool _hasValue = false; bool _hasValue = false;
String _op = '='; String _op = '=';
String _raw;
T _value; T _value;
@override @override
bool get hasValue => _hasValue; bool get hasValue => _hasValue;
bool _change(String op, T value) { bool _change(String op, T value) {
_raw = null;
_op = op; _op = op;
_value = value; _value = value;
return _hasValue = true; return _hasValue = true;
@ -25,6 +56,7 @@ class NumericSqlExpressionBuilder<T extends num>
@override @override
String compile() { String compile() {
if (_raw != null) return _raw;
if (_value == null) return null; if (_value == null) return null;
return '$_op $_value'; return '$_op $_value';
} }
@ -57,18 +89,41 @@ class NumericSqlExpressionBuilder<T extends num>
void notEquals(T value) { void notEquals(T value) {
_change('!=', value); _change('!=', value);
} }
@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;
}
} }
// TODO: Escape SQL Strings
class StringSqlExpressionBuilder implements SqlExpressionBuilder { class StringSqlExpressionBuilder implements SqlExpressionBuilder {
bool _hasValue = false; bool _hasValue = false;
String _op = '='; String _op = '=', _raw, _value;
String _value;
@override @override
bool get hasValue => _hasValue; bool get hasValue => _hasValue;
bool _change(String op, String value) { bool _change(String op, String value) {
_raw = null;
_op = op; _op = op;
_value = value; _value = value;
return _hasValue = true; return _hasValue = true;
@ -76,8 +131,9 @@ class StringSqlExpressionBuilder implements SqlExpressionBuilder {
@override @override
String compile() { String compile() {
if (_raw != null) return _raw;
if (_value == null) return null; if (_value == null) return null;
var v = _value.replaceAll("'", "\\'"); var v = sanitizeExpression(_value);
return "$_op '$v'"; return "$_op '$v'";
} }
@ -94,17 +150,48 @@ class StringSqlExpressionBuilder implements SqlExpressionBuilder {
void like(String value) { void like(String value) {
_change('LIKE', value); _change('LIKE', value);
} }
@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;
}
} }
class BooleanSqlExpressionBuilder implements SqlExpressionBuilder { class BooleanSqlExpressionBuilder implements SqlExpressionBuilder {
bool _hasValue = false; bool _hasValue = false;
String _op = '='; String _op = '=', _raw;
bool _value; bool _value;
@override @override
bool get hasValue => _hasValue; bool get hasValue => _hasValue;
bool _change(String op, bool value) { bool _change(String op, bool value) {
_raw = null;
_op = op; _op = op;
_value = value; _value = value;
return _hasValue = true; return _hasValue = true;
@ -112,6 +199,7 @@ class BooleanSqlExpressionBuilder implements SqlExpressionBuilder {
@override @override
String compile() { String compile() {
if (_raw != null) return _raw;
if (_value == null) return null; if (_value == null) return null;
var v = _value ? 'TRUE' : 'FALSE'; var v = _value ? 'TRUE' : 'FALSE';
return '$_op $v'; return '$_op $v';
@ -124,6 +212,28 @@ class BooleanSqlExpressionBuilder implements SqlExpressionBuilder {
void notEquals(bool value) { void notEquals(bool value) {
_change('!=', value); _change('!=', value);
} }
@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;
}
} }
class DateTimeSqlExpressionBuilder implements SqlExpressionBuilder { class DateTimeSqlExpressionBuilder implements SqlExpressionBuilder {
@ -180,6 +290,32 @@ class DateTimeSqlExpressionBuilder implements SqlExpressionBuilder {
_change('>=', value, includeTime != false); _change('>=', value, includeTime != false);
} }
@override
void isIn(@checked Iterable<DateTime> values) {
_raw = '"$columnName" IN (' +
values.map(DATE_YMD_HMS.format).map((s) => "'$s'").join(', ') +
')';
}
@override
void isNotIn(@checked Iterable<DateTime> values) {
_raw = '"$columnName" NOT IN (' +
values.map(DATE_YMD_HMS.format).map((s) => "'$s'").join(', ') +
')';
}
@override
void isBetween(@checked DateTime lower, @checked DateTime upper) {
var l = DATE_YMD_HMS.format(lower), u = DATE_YMD_HMS.format(upper);
_raw = "\"$columnName\" BETWEEN '$l' and '$u'";
}
@override
void isNotBetween(@checked DateTime lower, @checked DateTime upper) {
var l = DATE_YMD_HMS.format(lower), u = DATE_YMD_HMS.format(upper);
_raw = "\"$columnName\" NOT BETWEEN '$l' and '$u'";
}
@override @override
String compile() { String compile() {
if (_raw?.isNotEmpty == true) return _raw; if (_raw?.isNotEmpty == true) return _raw;

View file

@ -1,5 +1,5 @@
name: angel_orm name: angel_orm
version: 1.0.0-alpha+2 version: 1.0.0-alpha+3
description: Runtime support for Angel's ORM. description: Runtime support for Angel's ORM.
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
@ -7,5 +7,7 @@ environment:
sdk: ">=1.19.0" sdk: ">=1.19.0"
dependencies: dependencies:
intl: ">=0.0.0 <1.0.0" intl: ">=0.0.0 <1.0.0"
meta: ^1.0.0
pool: ^1.0.0 pool: ^1.0.0
postgres: ^0.9.5 postgres: ^0.9.5
string_scanner: ^1.0.0