Add `IS` operator to Dart query builder

This commit is contained in:
Simon Binder 2023-01-12 16:53:43 +01:00
parent 53c14cd1aa
commit 6e35abb9c9
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
6 changed files with 81 additions and 63 deletions

View File

@ -1,3 +1,8 @@
## 2.5.0-dev
- Add `isExp`, `isValue`, `isNotExp` and `isNotValue` methods to `Expression`
to generate the `IS` operator in SQL.
## 2.4.2
- Fix an exception when a client disconnects from a drift remote server while

View File

@ -42,6 +42,9 @@ abstract class Expression<D extends Object> implements FunctionParameter {
bool get isLiteral => false;
/// Whether this expression is equal to the given expression.
///
/// This generates an equals operator in SQL. To perform a comparison
/// sensitive to `NULL` values, use [isExp] instead.
Expression<bool> equalsExp(Expression<D> compare) =>
_Comparison.equal(this, compare);
@ -52,7 +55,7 @@ abstract class Expression<D extends Object> implements FunctionParameter {
///
/// This method only supports comparing the value of the column to non-
/// nullable values and translates to a direct `=` comparison in SQL.
/// To compare this column to `null`, use [equalsNullable].
/// To compare this column to `null`, use [isValue].
Expression<bool> equals(D compare) =>
_Comparison.equal(this, Variable<D>(compare));
@ -91,6 +94,51 @@ abstract class Expression<D extends Object> implements FunctionParameter {
return _CastInSqlExpression<D, D2>(this);
}
/// Generates an `IS` expression in SQL, comparing this expression with the
/// Dart [value].
///
/// This is the SQL method most closely resembling the [Object.==] operator in
/// Dart. When this expression and [value] are both non-null, this is the same
/// as [equals]. Two `NULL` values are considered equal as well.
Expression<bool> isValue(D value) {
return isExp(Variable<D>(value));
}
/// Generates an `IS NOT` expression in SQL, comparing this expression with
/// the Dart [value].
///
/// This the inverse of [isValue].
Expression<bool> isNotValue(D value) {
return isNotExp(Variable<D>(value));
}
/// Expression that is true if the inner expression resolves to a null value.
Expression<bool> isNull() => isExp(const Constant(null));
/// Expression that is true if the inner expression resolves to a non-null
/// value.
Expression<bool> isNotNull() => isNotExp(const Constant(null));
/// Generates an `IS` expression in SQL, comparing this expression with the
/// [other] expression.
///
/// This is the SQL method most closely resembling the [Object.==] operator in
/// Dart. When this expression and [other] are both non-null, this is the same
/// as [equalsExp]. Two `NULL` values are considered equal as well.
Expression<bool> isExp(Expression<D> other) {
return BaseInfixOperator(this, 'IS', other,
precedence: Precedence.comparisonEq);
}
/// Generates an `IS NOT` expression in SQL, comparing this expression with
/// the [other] expression.
///
/// This the inverse of [isExp].
Expression<bool> isNotExp(Expression<D> other) {
return BaseInfixOperator(this, 'IS NOT', other,
precedence: Precedence.comparisonEq);
}
/// An expression that is true if `this` resolves to any of the values in
/// [values].
Expression<bool> isIn(Iterable<D> values) {
@ -279,31 +327,27 @@ class Precedence implements Comparable<Precedence> {
/// [_Comparison].
enum _ComparisonOperator {
/// '<' in sql
less,
less('<'),
/// '<=' in sql
lessOrEqual,
lessOrEqual('<='),
/// '=' in sql
equal,
equal('='),
/// '>=' in sql
moreOrEqual,
moreOrEqual('>='),
/// '>' in sql
more
more('>');
final String operator;
const _ComparisonOperator(this.operator);
}
/// An expression that compares two child expressions.
class _Comparison extends InfixOperator<bool> {
static const Map<_ComparisonOperator, String> _operatorNames = {
_ComparisonOperator.less: '<',
_ComparisonOperator.lessOrEqual: '<=',
_ComparisonOperator.equal: '=',
_ComparisonOperator.moreOrEqual: '>=',
_ComparisonOperator.more: '>'
};
@override
final Expression left;
@override
@ -313,7 +357,7 @@ class _Comparison extends InfixOperator<bool> {
final _ComparisonOperator op;
@override
String get operator => _operatorNames[op]!;
String get operator => op.operator;
@override
Precedence get precedence {

View File

@ -1,24 +1,13 @@
part of '../query_builder.dart';
/// Expression that is true if the inner expression resolves to a null value.
@Deprecated('Use isNull through the SqlIsNull extension')
Expression<bool> isNull(Expression inner) => _NullCheck(inner, true);
@Deprecated('Use isNull on the Expression class')
Expression<bool> isNull(Expression inner) => inner.isNull();
/// Expression that is true if the inner expression resolves to a non-null
/// value.
@Deprecated('Use isNotNull through the SqlIsNull extension')
Expression<bool> isNotNull(Expression inner) => _NullCheck(inner, false);
/// Extension defines the `isNull` and `isNotNull` members to check whether the
/// expression evaluates to null or not.
extension SqlIsNull on Expression {
/// Expression that is true if the inner expression resolves to a null value.
Expression<bool> isNull() => _NullCheck(this, true);
/// Expression that is true if the inner expression resolves to a non-null
/// value.
Expression<bool> isNotNull() => _NullCheck(this, false);
}
@Deprecated('Use isNotNull on the Expression class')
Expression<bool> isNotNull(Expression inner) => inner.isNotNull();
/// Evaluates to the first expression in [expressions] that's not null, or
/// null if all [expressions] evaluate to null.
@ -35,34 +24,3 @@ Expression<T> ifNull<T extends Object>(
Expression<T> first, Expression<T> second) {
return FunctionCallExpression<T>('IFNULL', [first, second]);
}
class _NullCheck extends Expression<bool> {
final Expression _inner;
final bool _isNull;
@override
final Precedence precedence = Precedence.comparisonEq;
_NullCheck(this._inner, this._isNull);
@override
void writeInto(GenerationContext context) {
writeInner(context, _inner);
context.buffer.write(' IS ');
if (!_isNull) {
context.buffer.write('NOT ');
}
context.buffer.write('NULL');
}
@override
int get hashCode => Object.hash(_inner, _isNull);
@override
bool operator ==(Object other) {
return other is _NullCheck &&
other._inner == _inner &&
other._isNull == _isNull;
}
}

View File

@ -1,6 +1,6 @@
name: drift
description: Drift is a reactive library to store relational data in Dart and Flutter applications.
version: 2.4.2
version: 2.5.0-dev
repository: https://github.com/simolus3/drift
homepage: https://drift.simonbinder.eu/
issue_tracker: https://github.com/simolus3/drift/issues

View File

@ -120,4 +120,15 @@ void main() {
expect(a.equalsNullable(null), generates('a IS NULL'));
expect(a.equalsExp(b), generates('a = b'));
});
test('is', () {
const a = CustomExpression<int>('a', precedence: Precedence.primary);
const b = CustomExpression<int>('b', precedence: Precedence.primary);
expect(a.isValue(3), generates('a IS ?', [3]));
expect(a.isNotValue(3), generates('a IS NOT ?', [3]));
expect(a.isExp(b), generates('a IS b'));
expect(b.isNotExp(a), generates('b IS NOT a'));
});
}

View File

@ -25,7 +25,7 @@ dependencies:
io: ^1.0.3
# Drift-specific analysis and apis
drift: '>=2.4.0 <2.5.0'
drift: '>=2.5.0 <2.6.0'
sqlite3: '>=0.1.6 <2.0.0'
sqlparser: '^2.27.0-dev'