1.0.0-alpha+2
This commit is contained in:
parent
2a3040c9aa
commit
18946b4320
4 changed files with 170 additions and 10 deletions
4
angel_orm/CHANGELOG.md
Normal file
4
angel_orm/CHANGELOG.md
Normal 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`.
|
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
Loading…
Reference in a new issue