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
|
||||
* Split into `angel_orm.dart` and `server.dart`. Prevents DDC failures.
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# angel_orm
|
||||
Runtime support for Angel's ORM. Includes SQL expression generators, as well
|
||||
as a friendly `PostgreSQLConnectionPool` class that you can use to pool connections
|
||||
to a PostgreSQL database.
|
||||
Runtime support for Angel's ORM. Includes a clean, database-agnostic
|
||||
query builder and relationship/join support.
|
||||
|
||||
For documentation about the ORM, head to the main project repo:
|
||||
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/migration.dart';
|
||||
export 'src/relations.dart';
|
||||
export 'src/query.dart';
|
||||
export 'src/relations.dart';
|
|
@ -1,12 +1,31 @@
|
|||
const ORM orm = const ORM();
|
||||
|
||||
class ORM {
|
||||
final String tableName;
|
||||
const ORM([this.tableName]);
|
||||
/// The path to an Angel service that queries objects of the
|
||||
/// 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 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';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
/// Expects a field to be equal to a given [value].
|
||||
Predicate<T> equals<T>(T value) =>
|
||||
new Predicate<T>._(PredicateType.equals, value);
|
||||
|
||||
final DateFormat DATE_YMD = new DateFormat('yyyy-MM-dd');
|
||||
final DateFormat DATE_YMD_HMS = new DateFormat('yyyy-MM-dd HH:mm:ss');
|
||||
/// Expects at least one of the given [predicates] to be true.
|
||||
Predicate<T> anyOf<T>(Iterable<Predicate<T>> predicates) =>
|
||||
new MultiPredicate<T>._(PredicateType.any, predicates);
|
||||
|
||||
/// 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;
|
||||
/// Expects a field to be contained within a set of [values].
|
||||
Predicate<T> isIn<T>(Iterable<T> values) => new Predicate<T>._(PredicateType.isIn, null, values);
|
||||
|
||||
while (!scanner.isDone) {
|
||||
// Ignore comment starts
|
||||
if (scanner.scan('--') || scanner.scan('/*'))
|
||||
continue;
|
||||
/// Expects a field to be `null`.
|
||||
Predicate<T> isNull<T>() => equals(null);
|
||||
|
||||
// Ignore all single quotes and attempted escape sequences
|
||||
else if (scanner.scan("'") || scanner.scan('\\'))
|
||||
continue;
|
||||
/// Expects a given [predicate] to not be true.
|
||||
Predicate<T> not<T>(Predicate<T> predicate) =>
|
||||
new MultiPredicate<T>._(PredicateType.negate, [predicate]);
|
||||
|
||||
// Otherwise, add the next char, unless it's a null byte.
|
||||
else if ((ch = scanner.readChar()) != 0 && ch != null)
|
||||
buf.writeCharCode(ch);
|
||||
/// Expects a field to be not be `null`.
|
||||
Predicate<T> notNull<T>() => not(isNull());
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
/// A mechanism used to express an expectation about some object ([target]).
|
||||
class Predicate<T> {
|
||||
/// The type of expectation we are declaring.
|
||||
final PredicateType type;
|
||||
|
||||
/// The single argument of this target.
|
||||
final T target;
|
||||
final Iterable<T> args;
|
||||
|
||||
Predicate._(this.type, this.target, [this.args]);
|
||||
|
||||
Predicate<T> operator &(Predicate<T> other) => and(other);
|
||||
|
||||
Predicate<T> operator |(Predicate<T> other) => or(other);
|
||||
|
||||
Predicate<T> and(Predicate<T> other) {
|
||||
return new MultiPredicate._(PredicateType.and, [this, other]);
|
||||
}
|
||||
|
||||
abstract class SqlExpressionBuilder {
|
||||
bool get hasValue;
|
||||
String compile();
|
||||
void isBetween(lower, upper);
|
||||
void isNotBetween(lower, upper);
|
||||
void isIn(Iterable values);
|
||||
void isNotIn(Iterable values);
|
||||
}
|
||||
|
||||
class NumericSqlExpressionBuilder<T extends num>
|
||||
implements SqlExpressionBuilder {
|
||||
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(@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;
|
||||
Predicate<T> or(Predicate<T> other) {
|
||||
return new MultiPredicate._(PredicateType.or, [this, other]);
|
||||
}
|
||||
}
|
||||
|
||||
class StringSqlExpressionBuilder implements SqlExpressionBuilder {
|
||||
bool _hasValue = false;
|
||||
String _op = '=', _raw, _value;
|
||||
/// An advanced [Predicate] that performs an operation of multiple other predicates.
|
||||
class MultiPredicate<T> extends Predicate<T> {
|
||||
final Iterable<Predicate<T>> targets;
|
||||
|
||||
@override
|
||||
bool get hasValue => _hasValue;
|
||||
MultiPredicate._(PredicateType type, this.targets) : super._(type, null);
|
||||
|
||||
bool _change(String op, String value) {
|
||||
_raw = null;
|
||||
_op = op;
|
||||
_value = value;
|
||||
return _hasValue = true;
|
||||
/// Use [targets] instead.
|
||||
@deprecated
|
||||
T get target => throw new UnsupportedError(
|
||||
'IterablePredicate has no `target`. Use `targets` instead.');
|
||||
}
|
||||
|
||||
@override
|
||||
String compile() {
|
||||
if (_raw != null) return _raw;
|
||||
if (_value == null) return null;
|
||||
var v = sanitizeExpression(_value);
|
||||
return "$_op '$v'";
|
||||
/// The various types of predicate.
|
||||
enum PredicateType {
|
||||
equals,
|
||||
any,
|
||||
isIn,
|
||||
negate,
|
||||
and,
|
||||
or,
|
||||
less,
|
||||
greater,
|
||||
}
|
||||
|
||||
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 {
|
||||
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(@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 {
|
||||
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 ? 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 ');
|
||||
}
|
||||
}
|
||||
/// The various modes of sorting.
|
||||
enum SortType { ascending, descending }
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
name: angel_orm
|
||||
version: 1.0.0-alpha+12
|
||||
version: 1.0.0-alpha+11
|
||||
description: Runtime support for Angel's ORM.
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/orm
|
||||
environment:
|
||||
sdk: ">=1.19.0"
|
||||
sdk: '>=2.0.0-dev.1.2 <2.0.0'
|
||||
dependencies:
|
||||
intl: ">=0.0.0 <1.0.0"
|
||||
angel_model: ^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
|
||||
@serializable
|
||||
class _Order extends Model {
|
||||
@CanJoin(Customer, 'id')
|
||||
@Join(Customer, 'id')
|
||||
int customerId;
|
||||
int employeeId;
|
||||
DateTime orderDate;
|
||||
|
|
Loading…
Reference in a new issue