Use custom types for comparisons

This commit is contained in:
Simon Binder 2023-12-23 15:06:10 +01:00
parent ad856314c2
commit 615a1d7ad8
10 changed files with 130 additions and 24 deletions

View File

@ -1,3 +1,7 @@
## 2.15.0-dev
- Methods in the query builder API now respect custom types.
## 2.14.1 ## 2.14.1
- Fix `WasmProbeResult.open` ignoring the `ìnitializeDatabase` callback. - Fix `WasmProbeResult.open` ignoring the `ìnitializeDatabase` callback.

View File

@ -11,7 +11,7 @@ extension ComparableExpr<DT extends Comparable<dynamic>> on Expression<DT> {
/// Returns an expression that is true if this expression is strictly bigger /// Returns an expression that is true if this expression is strictly bigger
/// than the other value. /// than the other value.
Expression<bool> isBiggerThanValue(DT other) { Expression<bool> isBiggerThanValue(DT other) {
return isBiggerThan(Variable(other)); return isBiggerThan(variable(other));
} }
/// Returns an expression that is true if this expression is bigger than or /// Returns an expression that is true if this expression is bigger than or
@ -23,7 +23,7 @@ extension ComparableExpr<DT extends Comparable<dynamic>> on Expression<DT> {
/// Returns an expression that is true if this expression is bigger than or /// Returns an expression that is true if this expression is bigger than or
/// equal to he other value. /// equal to he other value.
Expression<bool> isBiggerOrEqualValue(DT other) { Expression<bool> isBiggerOrEqualValue(DT other) {
return isBiggerOrEqual(Variable(other)); return isBiggerOrEqual(variable(other));
} }
/// Returns an expression that is true if this expression is strictly smaller /// Returns an expression that is true if this expression is strictly smaller
@ -35,7 +35,7 @@ extension ComparableExpr<DT extends Comparable<dynamic>> on Expression<DT> {
/// Returns an expression that is true if this expression is strictly smaller /// Returns an expression that is true if this expression is strictly smaller
/// than the other value. /// than the other value.
Expression<bool> isSmallerThanValue(DT other) => Expression<bool> isSmallerThanValue(DT other) =>
isSmallerThan(Variable(other)); isSmallerThan(variable(other));
/// Returns an expression that is true if this expression is smaller than or /// Returns an expression that is true if this expression is smaller than or
/// equal to he other expression. /// equal to he other expression.
@ -46,7 +46,7 @@ extension ComparableExpr<DT extends Comparable<dynamic>> on Expression<DT> {
/// Returns an expression that is true if this expression is smaller than or /// Returns an expression that is true if this expression is smaller than or
/// equal to he other value. /// equal to he other value.
Expression<bool> isSmallerOrEqualValue(DT other) { Expression<bool> isSmallerOrEqualValue(DT other) {
return isSmallerOrEqual(Variable(other)); return isSmallerOrEqual(variable(other));
} }
/// Returns an expression evaluating to true if this expression is between /// Returns an expression evaluating to true if this expression is between
@ -67,8 +67,8 @@ extension ComparableExpr<DT extends Comparable<dynamic>> on Expression<DT> {
Expression<bool> isBetweenValues(DT lower, DT higher, {bool not = false}) { Expression<bool> isBetweenValues(DT lower, DT higher, {bool not = false}) {
return _BetweenExpression( return _BetweenExpression(
target: this, target: this,
lower: Variable<DT>(lower), lower: variable(lower),
higher: Variable<DT>(higher), higher: variable(higher),
not: not, not: not,
); );
} }

View File

@ -24,17 +24,27 @@ class CustomExpression<D extends Object> extends Expression<D> {
@override @override
final Precedence precedence; final Precedence precedence;
final CustomSqlType<D>? _customSqlType;
/// Constructs a custom expression by providing the raw sql [content]. /// Constructs a custom expression by providing the raw sql [content].
const CustomExpression(this.content, const CustomExpression(
{this.watchedTables = const [], this.precedence = Precedence.unknown}) this.content, {
: _dialectSpecificContent = null; this.watchedTables = const [],
this.precedence = Precedence.unknown,
CustomSqlType<D>? customType,
}) : _dialectSpecificContent = null,
_customSqlType = customType;
/// Constructs a custom expression providing the raw SQL in [content] depending /// Constructs a custom expression providing the raw SQL in [content] depending
/// on the SQL dialect when this expression is built. /// on the SQL dialect when this expression is built.
const CustomExpression.dialectSpecific(Map<SqlDialect, String> content, const CustomExpression.dialectSpecific(
{this.watchedTables = const [], this.precedence = Precedence.unknown}) Map<SqlDialect, String> content, {
: _dialectSpecificContent = content, this.watchedTables = const [],
content = ''; this.precedence = Precedence.unknown,
CustomSqlType<D>? customType,
}) : _dialectSpecificContent = content,
content = '',
_customSqlType = customType;
@override @override
void writeInto(GenerationContext context) { void writeInto(GenerationContext context) {
@ -53,6 +63,9 @@ class CustomExpression<D extends Object> extends Expression<D> {
@override @override
int get hashCode => content.hashCode * 3; int get hashCode => content.hashCode * 3;
@override
BaseSqlType<D> get driftSqlType => _customSqlType ?? super.driftSqlType;
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return other.runtimeType == runtimeType && return other.runtimeType == runtimeType &&

View File

@ -21,6 +21,7 @@ const Expression<DateTime> currentDate = _DependingOnDateTimeExpression(
'strftime', 'strftime',
[Constant('%s'), _currentDateLiteral], [Constant('%s'), _currentDateLiteral],
), ),
null,
), ),
); );

View File

@ -57,7 +57,7 @@ abstract class Expression<D extends Object> implements FunctionParameter {
/// nullable values and translates to a direct `=` comparison in SQL. /// nullable values and translates to a direct `=` comparison in SQL.
/// To compare this column to `null`, use [isValue]. /// To compare this column to `null`, use [isValue].
Expression<bool> equals(D compare) => Expression<bool> equals(D compare) =>
_Comparison.equal(this, Variable<D>(compare)); _Comparison.equal(this, variable(compare));
/// Compares the value of this column to [compare] or `null`. /// Compares the value of this column to [compare] or `null`.
/// ///
@ -81,8 +81,8 @@ abstract class Expression<D extends Object> implements FunctionParameter {
/// in sql, use [cast]. /// in sql, use [cast].
/// ///
/// This method is used internally by drift. /// This method is used internally by drift.
Expression<D2> dartCast<D2 extends Object>() { Expression<D2> dartCast<D2 extends Object>({CustomSqlType<D2>? customType}) {
return _DartCastExpression<D, D2>(this); return _DartCastExpression<D, D2>(this, customType);
} }
/// Generates a `CAST(expression AS TYPE)` expression. /// Generates a `CAST(expression AS TYPE)` expression.
@ -106,7 +106,7 @@ abstract class Expression<D extends Object> implements FunctionParameter {
/// Dart. When this expression and [value] are both non-null, this is the same /// Dart. When this expression and [value] are both non-null, this is the same
/// as [equals]. Two `NULL` values are considered equal as well. /// as [equals]. Two `NULL` values are considered equal as well.
Expression<bool> isValue(D value) { Expression<bool> isValue(D value) {
return isExp(Variable<D>(value)); return isExp(variable(value));
} }
/// Generates an `IS NOT` expression in SQL, comparing this expression with /// Generates an `IS NOT` expression in SQL, comparing this expression with
@ -114,7 +114,7 @@ abstract class Expression<D extends Object> implements FunctionParameter {
/// ///
/// This the inverse of [isValue]. /// This the inverse of [isValue].
Expression<bool> isNotValue(D value) { Expression<bool> isNotValue(D value) {
return isNotExp(Variable<D>(value)); return isNotExp(variable(value));
} }
/// Expression that is true if the inner expression resolves to a null value. /// Expression that is true if the inner expression resolves to a null value.
@ -147,13 +147,13 @@ abstract class Expression<D extends Object> implements FunctionParameter {
/// An expression that is true if `this` resolves to any of the values in /// An expression that is true if `this` resolves to any of the values in
/// [values]. /// [values].
Expression<bool> isIn(Iterable<D> values) { Expression<bool> isIn(Iterable<D> values) {
return isInExp([for (final value in values) Variable<D>(value)]); return isInExp([for (final value in values) variable(value)]);
} }
/// An expression that is true if `this` does not resolve to any of the values /// An expression that is true if `this` does not resolve to any of the values
/// in [values]. /// in [values].
Expression<bool> isNotIn(Iterable<D> values) { Expression<bool> isNotIn(Iterable<D> values) {
return isNotInExp([for (final value in values) Variable<D>(value)]); return isNotInExp([for (final value in values) variable(value)]);
} }
/// An expression that evaluates to `true` if this expression resolves to a /// An expression that evaluates to `true` if this expression resolves to a
@ -478,8 +478,12 @@ class _UnaryMinus<DT extends Object> extends Expression<DT> {
class _DartCastExpression<D1 extends Object, D2 extends Object> class _DartCastExpression<D1 extends Object, D2 extends Object>
extends Expression<D2> { extends Expression<D2> {
final Expression<D1> inner; final Expression<D1> inner;
final CustomSqlType<D2>? _customSqlType;
const _DartCastExpression(this.inner); const _DartCastExpression(this.inner, this._customSqlType);
@override
BaseSqlType<D2> get driftSqlType => _customSqlType ?? super.driftSqlType;
@override @override
Precedence get precedence => inner.precedence; Precedence get precedence => inner.precedence;

View File

@ -3,6 +3,7 @@ library;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../types/mapping.dart';
import 'query_builder.dart'; import 'query_builder.dart';
/// Internal utilities for building queries that aren't exported. /// Internal utilities for building queries that aren't exported.
@ -46,3 +47,15 @@ extension WriteDefinition on GenerationContext {
return sql.values.first; // Fallback return sql.values.first; // Fallback
} }
} }
/// Utilities to derive other expressions with a type compatible to `this`
/// expression.
extension WithTypes<T extends Object> on Expression<T> {
/// Creates a variable with a matching [driftSqlType].
Variable<T> variable(T? value) {
return switch (driftSqlType) {
CustomSqlType<T> custom => Variable(value, custom),
_ => Variable(value),
};
}
}

View File

@ -227,7 +227,7 @@ class GeneratedColumn<T extends Object> extends Column<T> {
} }
Variable _evaluateClientDefault() { Variable _evaluateClientDefault() {
return Variable<T>(clientDefault!()); return variable(clientDefault!());
} }
/// A value for [additionalChecks] validating allowed text lengths. /// A value for [additionalChecks] validating allowed text lengths.

View File

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

View File

@ -0,0 +1,71 @@
import 'package:drift/drift.dart';
import 'package:test/test.dart';
import '../../test_utils/test_utils.dart';
void main() {
const a = CustomExpression('a',
customType: _NegatedIntType(), precedence: Precedence.primary);
test('equals', () {
expect(a.equals(123), generates('a = ?', [-123]));
});
test('is', () {
expect(a.isValue(42), generates('a IS ?', [-42]));
expect(a.isNotValue(42), generates('a IS NOT ?', [-42]));
expect(a.isIn([1, 2, 3]), generates('a IN (?, ?, ?)', [-1, -2, -3]));
expect(a.isNotIn([1, 2, 3]), generates('a NOT IN (?, ?, ?)', [-1, -2, -3]));
});
test('comparison', () {
expect(a.isSmallerThanValue(42), generates('a < ?', [-42]));
expect(a.isSmallerOrEqualValue(42), generates('a <= ?', [-42]));
expect(a.isBiggerThanValue(42), generates('a > ?', [-42]));
expect(a.isBiggerOrEqualValue(42), generates('a >= ?', [-42]));
expect(
a.isBetweenValues(12, 24), generates('a BETWEEN ? AND ?', [-12, -24]));
expect(a.isBetweenValues(12, 24, not: true),
generates('a NOT BETWEEN ? AND ?', [-12, -24]));
});
test('cast', () {
expect(Variable.withInt(10).cast<int>(const _NegatedIntType()),
generates('CAST(? AS custom_int)', [10]));
});
test('dartCast', () {
final exp =
Variable.withInt(10).dartCast<int>(customType: const _NegatedIntType());
expect(exp, generates('?', [10]));
expect(exp.driftSqlType, isA<_NegatedIntType>());
});
}
class _NegatedIntType implements CustomSqlType<int> {
const _NegatedIntType();
@override
String mapToSqlLiteral(int dartValue) {
return '-$dartValue';
}
@override
Object mapToSqlParameter(int dartValue) {
return -dartValue;
}
@override
int read(Object fromSql) {
return -(fromSql as int);
}
@override
String sqlTypeName(GenerationContext context) {
return 'custom_int';
}
}

View File

@ -30,7 +30,7 @@ dependencies:
io: ^1.0.3 io: ^1.0.3
# Drift-specific analysis and apis # Drift-specific analysis and apis
drift: '>=2.14.0 <2.15.0' drift: '>=2.15.0 <2.16.0'
sqlite3: '>=0.1.6 <3.0.0' sqlite3: '>=0.1.6 <3.0.0'
sqlparser: '^0.33.0' sqlparser: '^0.33.0'