Remove Postgres from ORM, fluent query builder, example
This commit is contained in:
parent
8dff26a8c0
commit
9dd1dccf41
10 changed files with 159 additions and 512 deletions
|
@ -1,3 +1,9 @@
|
||||||
|
# 1.0.0-alpha+11
|
||||||
|
* Removed PostgreSQL-specific functionality, so that the ORM can ultimately
|
||||||
|
target all services.
|
||||||
|
* Created a better `Join` model.
|
||||||
|
* Created a far better `Query` model.
|
||||||
|
|
||||||
# 1.0.0-alpha+10
|
# 1.0.0-alpha+10
|
||||||
* Split into `angel_orm.dart` and `server.dart`. Prevents DDC failures.
|
* Split into `angel_orm.dart` and `server.dart`. Prevents DDC failures.
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# angel_orm
|
# angel_orm
|
||||||
Runtime support for Angel's ORM. Includes SQL expression generators, as well
|
Runtime support for Angel's ORM. Includes a clean, database-agnostic
|
||||||
as a friendly `PostgreSQLConnectionPool` class that you can use to pool connections
|
query builder and relationship/join support.
|
||||||
to a PostgreSQL database.
|
|
||||||
|
|
||||||
For documentation about the ORM, head to the main project repo:
|
For documentation about the ORM, head to the main project repo:
|
||||||
https://github.com/angel-dart/orm
|
https://github.com/angel-dart/orm
|
27
angel_orm/example/main.dart
Normal file
27
angel_orm/example/main.dart
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import 'package:angel_model/angel_model.dart';
|
||||||
|
import 'package:angel_orm/angel_orm.dart';
|
||||||
|
|
||||||
|
Query findEmployees(Company company) {
|
||||||
|
return new Query()
|
||||||
|
..['company_id'] = equals(company.id)
|
||||||
|
..['first_name'] = notNull() & (equals('John'))
|
||||||
|
..['salary'] = greaterThanOrEqual(100000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ORM('api/companies')
|
||||||
|
class Company extends Model {
|
||||||
|
String name;
|
||||||
|
bool isFortune500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@orm
|
||||||
|
class Employee extends Model {
|
||||||
|
@belongsTo
|
||||||
|
Company company;
|
||||||
|
|
||||||
|
String firstName, lastName;
|
||||||
|
|
||||||
|
double salary;
|
||||||
|
|
||||||
|
bool get isFortune500Employee => company.isFortune500;
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
export 'src/annotations.dart';
|
export 'src/annotations.dart';
|
||||||
export 'src/migration.dart';
|
export 'src/query.dart';
|
||||||
export 'src/relations.dart';
|
export 'src/relations.dart';
|
||||||
export 'src/query.dart';
|
|
|
@ -1,12 +1,31 @@
|
||||||
const ORM orm = const ORM();
|
const ORM orm = const ORM();
|
||||||
|
|
||||||
class ORM {
|
class ORM {
|
||||||
final String tableName;
|
/// The path to an Angel service that queries objects of the
|
||||||
const ORM([this.tableName]);
|
/// annotated type at runtime.
|
||||||
|
///
|
||||||
|
/// Ex. `api/users`, etc.
|
||||||
|
final String servicePath;
|
||||||
|
const ORM([this.servicePath]);
|
||||||
}
|
}
|
||||||
|
|
||||||
class CanJoin {
|
/// Specifies that the ORM should build a join builder
|
||||||
|
/// that combines the results of queries on two services.
|
||||||
|
class Join {
|
||||||
|
/// The [Model] type to join against.
|
||||||
final Type type;
|
final Type type;
|
||||||
final String foreignKey;
|
|
||||||
const CanJoin(this.type, this.foreignKey);
|
/// The path to an Angel service that queries objects of the
|
||||||
}
|
/// [type] being joined against, at runtime.
|
||||||
|
///
|
||||||
|
/// Ex. `api/users`, etc.
|
||||||
|
final String servicePath;
|
||||||
|
|
||||||
|
/// The type of join this is.
|
||||||
|
final JoinType joinType;
|
||||||
|
|
||||||
|
const Join(this.type, this.servicePath, [this.joinType = JoinType.join]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The various types of [Join].
|
||||||
|
enum JoinType { join, left, right, full, self }
|
||||||
|
|
|
@ -1,113 +0,0 @@
|
||||||
const List<String> SQL_RESERVED_WORDS = const [
|
|
||||||
'SELECT', 'UPDATE', 'INSERT', 'DELETE', 'FROM', 'ASC', 'DESC', 'VALUES', 'RETURNING', 'ORDER', 'BY',
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Applies additional attributes to a database column.
|
|
||||||
class Column {
|
|
||||||
/// If `true`, a SQL field will be nullable.
|
|
||||||
final bool nullable;
|
|
||||||
|
|
||||||
/// Specifies the length of a `VARCHAR`.
|
|
||||||
final int length;
|
|
||||||
|
|
||||||
/// Explicitly defines a SQL type for this column.
|
|
||||||
final ColumnType type;
|
|
||||||
|
|
||||||
/// Specifies what kind of index this column is, if any.
|
|
||||||
final IndexType index;
|
|
||||||
|
|
||||||
/// The default value of this field.
|
|
||||||
final defaultValue;
|
|
||||||
|
|
||||||
const Column(
|
|
||||||
{this.nullable: true,
|
|
||||||
this.length,
|
|
||||||
this.type,
|
|
||||||
this.index: IndexType.NONE,
|
|
||||||
this.defaultValue});
|
|
||||||
}
|
|
||||||
|
|
||||||
class PrimaryKey extends Column {
|
|
||||||
const PrimaryKey({ColumnType columnType})
|
|
||||||
: super(
|
|
||||||
type: columnType ?? ColumnType.SERIAL,
|
|
||||||
index: IndexType.PRIMARY_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Column primaryKey = const PrimaryKey();
|
|
||||||
|
|
||||||
/// Maps to SQL index types.
|
|
||||||
enum IndexType {
|
|
||||||
NONE,
|
|
||||||
|
|
||||||
/// Standard index.
|
|
||||||
INDEX,
|
|
||||||
|
|
||||||
/// A primary key.
|
|
||||||
PRIMARY_KEY,
|
|
||||||
|
|
||||||
/// A *unique* index.
|
|
||||||
UNIQUE
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps to SQL data types.
|
|
||||||
///
|
|
||||||
/// Features all types from this list: http://www.tutorialspoint.com/sql/sql-data-types.htm
|
|
||||||
class ColumnType {
|
|
||||||
/// The name of this data type.
|
|
||||||
final String name;
|
|
||||||
const ColumnType(this.name);
|
|
||||||
|
|
||||||
static const ColumnType BOOLEAN = const ColumnType('boolean');
|
|
||||||
|
|
||||||
static const ColumnType SMALL_SERIAL = const ColumnType('smallserial');
|
|
||||||
static const ColumnType SERIAL = const ColumnType('serial');
|
|
||||||
static const ColumnType BIG_SERIAL = const ColumnType('bigserial');
|
|
||||||
|
|
||||||
// Numbers
|
|
||||||
static const ColumnType BIG_INT = const ColumnType('bigint');
|
|
||||||
static const ColumnType INT = const ColumnType('int');
|
|
||||||
static const ColumnType SMALL_INT = const ColumnType('smallint');
|
|
||||||
static const ColumnType TINY_INT = const ColumnType('tinyint');
|
|
||||||
static const ColumnType BIT = const ColumnType('bit');
|
|
||||||
static const ColumnType DECIMAL = const ColumnType('decimal');
|
|
||||||
static const ColumnType NUMERIC = const ColumnType('numeric');
|
|
||||||
static const ColumnType MONEY = const ColumnType('money');
|
|
||||||
static const ColumnType SMALL_MONEY = const ColumnType('smallmoney');
|
|
||||||
static const ColumnType FLOAT = const ColumnType('float');
|
|
||||||
static const ColumnType REAL = const ColumnType('real');
|
|
||||||
|
|
||||||
// Dates and times
|
|
||||||
static const ColumnType DATE_TIME = const ColumnType('datetime');
|
|
||||||
static const ColumnType SMALL_DATE_TIME = const ColumnType('smalldatetime');
|
|
||||||
static const ColumnType DATE = const ColumnType('date');
|
|
||||||
static const ColumnType TIME = const ColumnType('time');
|
|
||||||
static const ColumnType TIME_STAMP = const ColumnType('timestamp');
|
|
||||||
static const ColumnType TIME_STAMP_WITH_TIME_ZONE = const ColumnType('timestamp with time zone');
|
|
||||||
|
|
||||||
// Strings
|
|
||||||
static const ColumnType CHAR = const ColumnType('char');
|
|
||||||
static const ColumnType VAR_CHAR = const ColumnType('varchar');
|
|
||||||
static const ColumnType VAR_CHAR_MAX = const ColumnType('varchar(max)');
|
|
||||||
static const ColumnType TEXT = const ColumnType('text');
|
|
||||||
|
|
||||||
// Unicode strings
|
|
||||||
static const ColumnType NCHAR = const ColumnType('nchar');
|
|
||||||
static const ColumnType NVAR_CHAR = const ColumnType('nvarchar');
|
|
||||||
static const ColumnType NVAR_CHAR_MAX = const ColumnType('nvarchar(max)');
|
|
||||||
static const ColumnType NTEXT = const ColumnType('ntext');
|
|
||||||
|
|
||||||
// Binary
|
|
||||||
static const ColumnType BINARY = const ColumnType('binary');
|
|
||||||
static const ColumnType VAR_BINARY = const ColumnType('varbinary');
|
|
||||||
static const ColumnType VAR_BINARY_MAX = const ColumnType('varbinary(max)');
|
|
||||||
static const ColumnType IMAGE = const ColumnType('image');
|
|
||||||
|
|
||||||
// Misc.
|
|
||||||
static const ColumnType SQL_VARIANT = const ColumnType('sql_variant');
|
|
||||||
static const ColumnType UNIQUE_IDENTIFIER =
|
|
||||||
const ColumnType('uniqueidentifier');
|
|
||||||
static const ColumnType XML = const ColumnType('xml');
|
|
||||||
static const ColumnType CURSOR = const ColumnType('cursor');
|
|
||||||
static const ColumnType TABLE = const ColumnType('table');
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'package:pool/pool.dart';
|
|
||||||
import 'package:postgres/postgres.dart';
|
|
||||||
|
|
||||||
/// Connects to a PostgreSQL database, whether synchronously or asynchronously.
|
|
||||||
typedef FutureOr<PostgreSQLConnection> PostgreSQLConnector();
|
|
||||||
|
|
||||||
/// Pools connections to a PostgreSQL database.
|
|
||||||
class PostgreSQLConnectionPool {
|
|
||||||
final List<PostgreSQLConnection> _connections = [];
|
|
||||||
final List<int> _opened = [];
|
|
||||||
int _index = 0;
|
|
||||||
Pool _pool;
|
|
||||||
|
|
||||||
/// The maximum number of concurrent connections to the database.
|
|
||||||
///
|
|
||||||
/// Default: `5`
|
|
||||||
final int concurrency;
|
|
||||||
|
|
||||||
/// An optional timeout for pooled connections to execute.
|
|
||||||
final Duration timeout;
|
|
||||||
|
|
||||||
/// A function that connects this pool to the database, on-demand.
|
|
||||||
final PostgreSQLConnector connector;
|
|
||||||
|
|
||||||
PostgreSQLConnectionPool(this.connector,
|
|
||||||
{this.concurrency: 5, this.timeout}) {
|
|
||||||
_pool = new Pool(concurrency, timeout: timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<PostgreSQLConnection> _connect() async {
|
|
||||||
if (_connections.isEmpty) {
|
|
||||||
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();
|
|
||||||
_opened.add(connection.hashCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future close() => Future.wait(_connections.map((c) => c.close()));
|
|
||||||
|
|
||||||
/// Connects to the database, and then executes the [callback].
|
|
||||||
///
|
|
||||||
/// Returns the result of [callback].
|
|
||||||
Future<T> run<T>(FutureOr<T> callback(PostgreSQLConnection connection)) {
|
|
||||||
return _pool.request().then((resx) {
|
|
||||||
return _connect().then((connection) {
|
|
||||||
return new Future<T>.sync(() => callback(connection))
|
|
||||||
.whenComplete(() async {
|
|
||||||
if (connection.isClosed) {
|
|
||||||
_connections
|
|
||||||
..remove(connection)
|
|
||||||
..add(await connector());
|
|
||||||
}
|
|
||||||
resx.release();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,332 +1,114 @@
|
||||||
import 'package:meta/meta.dart';
|
/// Expects a field to be equal to a given [value].
|
||||||
import 'package:intl/intl.dart';
|
Predicate<T> equals<T>(T value) =>
|
||||||
import 'package:string_scanner/string_scanner.dart';
|
new Predicate<T>._(PredicateType.equals, value);
|
||||||
|
|
||||||
final DateFormat DATE_YMD = new DateFormat('yyyy-MM-dd');
|
/// Expects at least one of the given [predicates] to be true.
|
||||||
final DateFormat DATE_YMD_HMS = new DateFormat('yyyy-MM-dd HH:mm:ss');
|
Predicate<T> anyOf<T>(Iterable<Predicate<T>> predicates) =>
|
||||||
|
new MultiPredicate<T>._(PredicateType.any, predicates);
|
||||||
|
|
||||||
/// Cleans an input SQL expression of common SQL injection points.
|
/// Expects a field to be contained within a set of [values].
|
||||||
String sanitizeExpression(String unsafe) {
|
Predicate<T> isIn<T>(Iterable<T> values) => new Predicate<T>._(PredicateType.isIn, null, values);
|
||||||
var buf = new StringBuffer();
|
|
||||||
var scanner = new StringScanner(unsafe);
|
|
||||||
int ch;
|
|
||||||
|
|
||||||
while (!scanner.isDone) {
|
/// Expects a field to be `null`.
|
||||||
// Ignore comment starts
|
Predicate<T> isNull<T>() => equals(null);
|
||||||
if (scanner.scan('--') || scanner.scan('/*'))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Ignore all single quotes and attempted escape sequences
|
/// Expects a given [predicate] to not be true.
|
||||||
else if (scanner.scan("'") || scanner.scan('\\'))
|
Predicate<T> not<T>(Predicate<T> predicate) =>
|
||||||
continue;
|
new MultiPredicate<T>._(PredicateType.negate, [predicate]);
|
||||||
|
|
||||||
// Otherwise, add the next char, unless it's a null byte.
|
/// Expects a field to be not be `null`.
|
||||||
else if ((ch = scanner.readChar()) != 0 && ch != null)
|
Predicate<T> notNull<T>() => not(isNull());
|
||||||
buf.writeCharCode(ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.toString();
|
/// Expects a field to be less than a given [value].
|
||||||
|
Predicate<T> lessThan<T>(T value) =>
|
||||||
|
new Predicate<T>._(PredicateType.less, value);
|
||||||
|
|
||||||
|
/// Expects a field to be less than or equal to a given [value].
|
||||||
|
Predicate<T> lessThanOrEqual<T>(T value) => lessThan(value) | equals(value);
|
||||||
|
|
||||||
|
/// Expects a field to be greater than a given [value].
|
||||||
|
Predicate<T> greaterThan<T>(T value) =>
|
||||||
|
new Predicate<T>._(PredicateType.greater, value);
|
||||||
|
|
||||||
|
/// Expects a field to be greater than or equal to a given [value].
|
||||||
|
Predicate<T> greaterThanOrEqual<T>(T value) =>
|
||||||
|
greaterThan(value) | equals(value);
|
||||||
|
|
||||||
|
/// A generic query class.
|
||||||
|
///
|
||||||
|
/// Angel services can translate these into driver-specific queries.
|
||||||
|
/// This allows the Angel ORM to be flexible and support multiple platforms.
|
||||||
|
class Query {
|
||||||
|
final Map<String, Predicate> _fields = {};
|
||||||
|
final Map<String, SortType> _sort = {};
|
||||||
|
|
||||||
|
/// Each field in a query is actually a [Predicate], and therefore acts as a contract
|
||||||
|
/// with the underlying service.
|
||||||
|
Map<String, Predicate> get fields =>
|
||||||
|
new Map<String, Predicate>.unmodifiable(_fields);
|
||||||
|
|
||||||
|
/// The sorting order applied to this query.
|
||||||
|
Map<String, SortType> get sorting =>
|
||||||
|
new Map<String, SortType>.unmodifiable(_sort);
|
||||||
|
|
||||||
|
/// Sets the [Predicate] assigned to the given [key].
|
||||||
|
void operator []=(String key, Predicate value) => _fields[key] = value;
|
||||||
|
|
||||||
|
/// Gets the [Predicate] assigned to the given [key].
|
||||||
|
Predicate operator [](String key) => _fields[key];
|
||||||
|
|
||||||
|
/// Sort output by the given [key].
|
||||||
|
void sortBy(String key, [SortType type = SortType.descending]) =>
|
||||||
|
_sort[key] = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class SqlExpressionBuilder {
|
/// A mechanism used to express an expectation about some object ([target]).
|
||||||
bool get hasValue;
|
class Predicate<T> {
|
||||||
String compile();
|
/// The type of expectation we are declaring.
|
||||||
void isBetween(lower, upper);
|
final PredicateType type;
|
||||||
void isNotBetween(lower, upper);
|
|
||||||
void isIn(Iterable values);
|
|
||||||
void isNotIn(Iterable values);
|
|
||||||
}
|
|
||||||
|
|
||||||
class NumericSqlExpressionBuilder<T extends num>
|
/// The single argument of this target.
|
||||||
implements SqlExpressionBuilder {
|
final T target;
|
||||||
bool _hasValue = false;
|
final Iterable<T> args;
|
||||||
String _op = '=';
|
|
||||||
String _raw;
|
|
||||||
T _value;
|
|
||||||
|
|
||||||
@override
|
Predicate._(this.type, this.target, [this.args]);
|
||||||
bool get hasValue => _hasValue;
|
|
||||||
|
|
||||||
bool _change(String op, T value) {
|
Predicate<T> operator &(Predicate<T> other) => and(other);
|
||||||
_raw = null;
|
|
||||||
_op = op;
|
Predicate<T> operator |(Predicate<T> other) => or(other);
|
||||||
_value = value;
|
|
||||||
return _hasValue = true;
|
Predicate<T> and(Predicate<T> other) {
|
||||||
|
return new MultiPredicate._(PredicateType.and, [this, other]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Predicate<T> or(Predicate<T> other) {
|
||||||
String compile() {
|
return new MultiPredicate._(PredicateType.or, [this, other]);
|
||||||
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(@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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StringSqlExpressionBuilder implements SqlExpressionBuilder {
|
/// An advanced [Predicate] that performs an operation of multiple other predicates.
|
||||||
bool _hasValue = false;
|
class MultiPredicate<T> extends Predicate<T> {
|
||||||
String _op = '=', _raw, _value;
|
final Iterable<Predicate<T>> targets;
|
||||||
|
|
||||||
@override
|
MultiPredicate._(PredicateType type, this.targets) : super._(type, null);
|
||||||
bool get hasValue => _hasValue;
|
|
||||||
|
|
||||||
bool _change(String op, String value) {
|
/// Use [targets] instead.
|
||||||
_raw = null;
|
@deprecated
|
||||||
_op = op;
|
T get target => throw new UnsupportedError(
|
||||||
_value = value;
|
'IterablePredicate has no `target`. Use `targets` instead.');
|
||||||
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(@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 {
|
/// The various types of predicate.
|
||||||
bool _hasValue = false;
|
enum PredicateType {
|
||||||
String _op = '=', _raw;
|
equals,
|
||||||
bool _value;
|
any,
|
||||||
|
isIn,
|
||||||
@override
|
negate,
|
||||||
bool get hasValue => _hasValue;
|
and,
|
||||||
|
or,
|
||||||
bool _change(String op, bool value) {
|
less,
|
||||||
_raw = null;
|
greater,
|
||||||
_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(@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 {
|
/// The various modes of sorting.
|
||||||
final NumericSqlExpressionBuilder<int> year =
|
enum SortType { ascending, descending }
|
||||||
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 ? DATE_YMD_HMS.format(dt) : DATE_YMD.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(@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
|
|
||||||
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,13 +1,10 @@
|
||||||
name: angel_orm
|
name: angel_orm
|
||||||
version: 1.0.0-alpha+12
|
version: 1.0.0-alpha+11
|
||||||
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
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=1.19.0"
|
sdk: '>=2.0.0-dev.1.2 <2.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
intl: ">=0.0.0 <1.0.0"
|
angel_model: ^1.0.0
|
||||||
meta: ^1.0.0
|
meta: ^1.0.0
|
||||||
pool: ^1.0.0
|
|
||||||
postgres: ^0.9.5
|
|
||||||
string_scanner: ^1.0.0
|
|
|
@ -9,7 +9,7 @@ part 'order.g.dart';
|
||||||
@orm
|
@orm
|
||||||
@serializable
|
@serializable
|
||||||
class _Order extends Model {
|
class _Order extends Model {
|
||||||
@CanJoin(Customer, 'id')
|
@Join(Customer, 'id')
|
||||||
int customerId;
|
int customerId;
|
||||||
int employeeId;
|
int employeeId;
|
||||||
DateTime orderDate;
|
DateTime orderDate;
|
||||||
|
|
Loading…
Reference in a new issue