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
- 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
/// than the other value.
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
@ -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
/// equal to he other value.
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
@ -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
/// than the other value.
Expression<bool> isSmallerThanValue(DT other) =>
isSmallerThan(Variable(other));
isSmallerThan(variable(other));
/// Returns an expression that is true if this expression is smaller than or
/// 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
/// equal to he other value.
Expression<bool> isSmallerOrEqualValue(DT other) {
return isSmallerOrEqual(Variable(other));
return isSmallerOrEqual(variable(other));
}
/// 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}) {
return _BetweenExpression(
target: this,
lower: Variable<DT>(lower),
higher: Variable<DT>(higher),
lower: variable(lower),
higher: variable(higher),
not: not,
);
}

View File

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

View File

@ -21,6 +21,7 @@ const Expression<DateTime> currentDate = _DependingOnDateTimeExpression(
'strftime',
[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.
/// To compare this column to `null`, use [isValue].
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`.
///
@ -81,8 +81,8 @@ abstract class Expression<D extends Object> implements FunctionParameter {
/// in sql, use [cast].
///
/// This method is used internally by drift.
Expression<D2> dartCast<D2 extends Object>() {
return _DartCastExpression<D, D2>(this);
Expression<D2> dartCast<D2 extends Object>({CustomSqlType<D2>? customType}) {
return _DartCastExpression<D, D2>(this, customType);
}
/// 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
/// as [equals]. Two `NULL` values are considered equal as well.
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
@ -114,7 +114,7 @@ abstract class Expression<D extends Object> implements FunctionParameter {
///
/// This the inverse of [isValue].
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.
@ -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
/// [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
/// in [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
@ -478,8 +478,12 @@ class _UnaryMinus<DT extends Object> extends Expression<DT> {
class _DartCastExpression<D1 extends Object, D2 extends Object>
extends Expression<D2> {
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
Precedence get precedence => inner.precedence;

View File

@ -3,6 +3,7 @@ library;
import 'package:meta/meta.dart';
import '../types/mapping.dart';
import 'query_builder.dart';
/// Internal utilities for building queries that aren't exported.
@ -46,3 +47,15 @@ extension WriteDefinition on GenerationContext {
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() {
return Variable<T>(clientDefault!());
return variable(clientDefault!());
}
/// A value for [additionalChecks] validating allowed text lengths.

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.14.1
version: 2.15.0-dev
repository: https://github.com/simolus3/drift
homepage: https://drift.simonbinder.eu/
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
# 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'
sqlparser: '^0.33.0'