diff --git a/drift/CHANGELOG.md b/drift/CHANGELOG.md index 311c2a26..c144fd8f 100644 --- a/drift/CHANGELOG.md +++ b/drift/CHANGELOG.md @@ -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 diff --git a/drift/lib/src/runtime/query_builder/expressions/expression.dart b/drift/lib/src/runtime/query_builder/expressions/expression.dart index 3313c5b1..49017df0 100644 --- a/drift/lib/src/runtime/query_builder/expressions/expression.dart +++ b/drift/lib/src/runtime/query_builder/expressions/expression.dart @@ -42,6 +42,9 @@ abstract class Expression 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 equalsExp(Expression compare) => _Comparison.equal(this, compare); @@ -52,7 +55,7 @@ abstract class Expression 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 equals(D compare) => _Comparison.equal(this, Variable(compare)); @@ -91,6 +94,51 @@ abstract class Expression implements FunctionParameter { return _CastInSqlExpression(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 isValue(D value) { + return isExp(Variable(value)); + } + + /// Generates an `IS NOT` expression in SQL, comparing this expression with + /// the Dart [value]. + /// + /// This the inverse of [isValue]. + Expression isNotValue(D value) { + return isNotExp(Variable(value)); + } + + /// Expression that is true if the inner expression resolves to a null value. + Expression isNull() => isExp(const Constant(null)); + + /// Expression that is true if the inner expression resolves to a non-null + /// value. + Expression 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 isExp(Expression 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 isNotExp(Expression 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 isIn(Iterable values) { @@ -279,31 +327,27 @@ class Precedence implements Comparable { /// [_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 { - 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 { final _ComparisonOperator op; @override - String get operator => _operatorNames[op]!; + String get operator => op.operator; @override Precedence get precedence { diff --git a/drift/lib/src/runtime/query_builder/expressions/null_check.dart b/drift/lib/src/runtime/query_builder/expressions/null_check.dart index 01284a8d..7f4b8ee5 100644 --- a/drift/lib/src/runtime/query_builder/expressions/null_check.dart +++ b/drift/lib/src/runtime/query_builder/expressions/null_check.dart @@ -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 isNull(Expression inner) => _NullCheck(inner, true); +@Deprecated('Use isNull on the Expression class') +Expression 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 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 isNull() => _NullCheck(this, true); - - /// Expression that is true if the inner expression resolves to a non-null - /// value. - Expression isNotNull() => _NullCheck(this, false); -} +@Deprecated('Use isNotNull on the Expression class') +Expression 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 ifNull( Expression first, Expression second) { return FunctionCallExpression('IFNULL', [first, second]); } - -class _NullCheck extends Expression { - 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; - } -} diff --git a/drift/pubspec.yaml b/drift/pubspec.yaml index 42434714..aac00509 100644 --- a/drift/pubspec.yaml +++ b/drift/pubspec.yaml @@ -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 diff --git a/drift/test/database/expressions/expression_test.dart b/drift/test/database/expressions/expression_test.dart index a5ce2694..fa72aa57 100644 --- a/drift/test/database/expressions/expression_test.dart +++ b/drift/test/database/expressions/expression_test.dart @@ -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('a', precedence: Precedence.primary); + const b = CustomExpression('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')); + }); } diff --git a/drift_dev/pubspec.yaml b/drift_dev/pubspec.yaml index 784d7e63..901665d8 100644 --- a/drift_dev/pubspec.yaml +++ b/drift_dev/pubspec.yaml @@ -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'