Skip to content

Commit

Permalink
Use custom types for comparisons
Browse files Browse the repository at this point in the history
  • Loading branch information
simolus3 committed Dec 23, 2023
1 parent ad85631 commit 615a1d7
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 24 deletions.
4 changes: 4 additions & 0 deletions drift/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
12 changes: 6 additions & 6 deletions drift/lib/src/runtime/query_builder/expressions/comparable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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,
);
}
Expand Down
27 changes: 20 additions & 7 deletions drift/lib/src/runtime/query_builder/expressions/custom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const Expression<DateTime> currentDate = _DependingOnDateTimeExpression(
'strftime',
[Constant('%s'), _currentDateLiteral],
),
null,
),
);

Expand Down
20 changes: 12 additions & 8 deletions drift/lib/src/runtime/query_builder/expressions/expression.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
///
Expand All @@ -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.
Expand All @@ -106,15 +106,15 @@ 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
/// the Dart [value].
///
/// 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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions drift/lib/src/runtime/query_builder/helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion drift/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
71 changes: 71 additions & 0 deletions drift/test/database/expressions/custom_types_test.dart
Original file line number Diff line number Diff line change
@@ -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';
}
}
2 changes: 1 addition & 1 deletion drift_dev/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down

0 comments on commit 615a1d7

Please sign in to comment.