Add BigInt aggregations, arithemtics and tests

This commit is contained in:
westito 2022-05-12 23:09:04 +02:00
parent 2cfd2b41a3
commit f72f14f3a5
9 changed files with 163 additions and 5 deletions

View File

@ -112,6 +112,49 @@ extension ArithmeticAggregates<DT extends num> on Expression<DT?> {
_AggregateExpression('TOTAL', [this], filter: filter);
}
/// Provides aggregate functions that are available for BigInt expressions.
extension BigIntAggregates<DT extends BigInt> on Expression<DT?> {
/// Return the average of all non-null values in this group.
///
/// {@macro drift_aggregate_filter}
Expression<double?> avg({Expression<bool?>? filter}) =>
_AggregateExpression('AVG', [this], filter: filter);
/// Return the maximum of all non-null values in this group.
///
/// If there are no non-null values in the group, returns null.
/// {@macro drift_aggregate_filter}
Expression<DT?> max({Expression<bool?>? filter}) =>
_AggregateExpression('MAX', [this], filter: filter);
/// Return the minimum of all non-null values in this group.
///
/// If there are no non-null values in the group, returns null.
/// {@macro drift_aggregate_filter}
Expression<DT?> min({Expression<bool?>? filter}) =>
_AggregateExpression('MIN', [this], filter: filter);
/// Calculate the sum of all non-null values in the group.
///
/// If all values are null, evaluates to null as well. If an overflow occurs
/// during calculation, sqlite will terminate the query with an "integer
/// overflow" exception.
///
/// See also [total], which behaves similarly but returns a floating point
/// value and doesn't throw an overflow exception.
/// {@macro drift_aggregate_filter}
Expression<DT?> sum({Expression<bool?>? filter}) =>
_AggregateExpression('SUM', [this], filter: filter);
/// Calculate the sum of all non-null values in the group.
///
/// If all values in the group are null, [total] returns `0.0`. This function
/// uses floating-point values internally.
/// {@macro drift_aggregate_filter}
Expression<double?> total({Expression<bool?>? filter}) =>
_AggregateExpression('TOTAL', [this], filter: filter);
}
/// Provides aggregate functions that are available on date time expressions.
extension DateTimeAggregate on Expression<DateTime?> {
/// Return the average of all non-null values in this group.

View File

@ -41,3 +41,45 @@ extension ArithmeticExpr<DT extends num?> on Expression<DT> {
return FunctionCallExpression('round', [this]).cast<int>();
}
}
/// Defines the `-`, `*` and `/` operators on sql expressions that support it.
extension ArithmeticBigIntExpr<DT extends BigInt?> on Expression<DT> {
/// Performs an addition (`this` + [other]) in sql.
Expression<DT> operator +(Expression<DT> other) {
return _BaseInfixOperator(this, '+', other,
precedence: Precedence.plusMinus);
}
/// Performs a subtraction (`this` - [other]) in sql.
Expression<DT> operator -(Expression<DT> other) {
return _BaseInfixOperator(this, '-', other,
precedence: Precedence.plusMinus);
}
/// Returns the negation of this value.
Expression<DT> operator -() {
return _UnaryMinus(this);
}
/// Performs a multiplication (`this` * [other]) in sql.
Expression<DT> operator *(Expression<DT> other) {
return _BaseInfixOperator(this, '*', other,
precedence: Precedence.mulDivide);
}
/// Performs a division (`this` / [other]) in sql.
Expression<DT> operator /(Expression<DT> other) {
return _BaseInfixOperator(this, '/', other,
precedence: Precedence.mulDivide);
}
/// Calculates the absolute value of this number.
Expression<DT> abs() {
return FunctionCallExpression('abs', [this]);
}
/// Rounds this expression to the nearest integer.
Expression<int?> roundToInt() {
return FunctionCallExpression('round', [this]).cast<int>();
}
}

View File

@ -36,3 +36,8 @@ dependency_overrides:
path: ../drift_dev
sqlparser:
path: ../sqlparser
sqlite3:
git:
url: https://github.com/simolus3/sqlite3.dart
ref: main
path: sqlite3

View File

@ -6,6 +6,8 @@ import '../../test_utils/test_utils.dart';
void main() {
const i1 = CustomExpression<int>('i1', precedence: Precedence.primary);
const i2 = CustomExpression<int>('i2', precedence: Precedence.primary);
const b1 = CustomExpression<BigInt>('b1', precedence: Precedence.primary);
const b2 = CustomExpression<BigInt>('b2', precedence: Precedence.primary);
const s1 = CustomExpression<String>('s1', precedence: Precedence.primary);
const s2 = CustomExpression<String>('s2', precedence: Precedence.primary);
@ -21,6 +23,18 @@ void main() {
expectNotEquals(i1 + i2, i2 + i1);
});
test('BigInt arithmetic test', () {
expect(b1 + b2 * b1, generates('b1 + b2 * b1'));
expect(b1 + b2 * b1, generates('b1 + b2 * b1'));
expect((b1 + b2) * b1, generates('(b1 + b2) * b1'));
expect(b1 - b2, generates('b1 - b2'));
expect(b1 - -b2, generates('b1 - -b2'));
expect(b1 / b2, generates('b1 / b2'));
expectEquals(i1 + i2, i1 + i2);
expectNotEquals(i1 + i2, i2 + i1);
});
test('string concatenation', () {
expect(s1 + s2, generates('s1 || s2'));
});

View File

@ -32,6 +32,10 @@ void main() {
expect(const Variable<dynamic>(123), generates('?', [123]));
});
test('big int', () {
expect(Variable<dynamic>(BigInt.from(123)), generates('?', [123]));
});
test('date time', () {
const stamp = 12345678;
final dateTime = DateTime.fromMillisecondsSinceEpoch(stamp * 1000);

View File

@ -40,6 +40,17 @@ void main() {
[42, 3.1415, anything]));
});
test('can insert BigInt values', () async {
await db.into(db.tableWithoutPK).insert(CustomRowClass.map(42, 0,
webSafeInt: BigInt.one, custom: MyCustomObject('custom'))
.toInsertable());
verify(executor.runInsert(
'INSERT INTO table_without_p_k '
'(not_really_an_id, some_float, custom) VALUES (?, ?, ?)',
[42, 3.1415, anything]));
});
test('generates insert or replace statements', () async {
await db.into(db.todosTable).insert(
TodoEntry(

View File

@ -70,6 +70,7 @@ const _uuid = Uuid();
class TableWithoutPK extends Table {
IntColumn get notReallyAnId => integer()();
RealColumn get someFloat => real()();
Int64Column get webSafeInt => int64().nullable()();
TextColumn get custom =>
text().map(const CustomConverter()).clientDefault(_uuid.v4)();
@ -78,18 +79,20 @@ class TableWithoutPK extends Table {
class CustomRowClass {
final int notReallyAnId;
final double anotherName;
final BigInt? webSafeInt;
final MyCustomObject custom;
final String? notFromDb;
double get someFloat => anotherName;
CustomRowClass._(
this.notReallyAnId, this.anotherName, this.custom, this.notFromDb);
CustomRowClass._(this.notReallyAnId, this.anotherName, this.webSafeInt,
this.custom, this.notFromDb);
factory CustomRowClass.map(int notReallyAnId, double someFloat,
{required MyCustomObject custom, String? notFromDb}) {
return CustomRowClass._(notReallyAnId, someFloat, custom, notFromDb);
{required MyCustomObject custom, BigInt? webSafeInt, String? notFromDb}) {
return CustomRowClass._(
notReallyAnId, someFloat, webSafeInt, custom, notFromDb);
}
}

View File

@ -1053,26 +1053,31 @@ class $SharedTodosTable extends SharedTodos
class TableWithoutPKCompanion extends UpdateCompanion<CustomRowClass> {
final Value<int> notReallyAnId;
final Value<double> someFloat;
final Value<BigInt?> webSafeInt;
final Value<MyCustomObject> custom;
const TableWithoutPKCompanion({
this.notReallyAnId = const Value.absent(),
this.someFloat = const Value.absent(),
this.webSafeInt = const Value.absent(),
this.custom = const Value.absent(),
});
TableWithoutPKCompanion.insert({
required int notReallyAnId,
required double someFloat,
this.webSafeInt = const Value.absent(),
this.custom = const Value.absent(),
}) : notReallyAnId = Value(notReallyAnId),
someFloat = Value(someFloat);
static Insertable<CustomRowClass> createCustom({
Expression<int>? notReallyAnId,
Expression<double>? someFloat,
Expression<BigInt?>? webSafeInt,
Expression<MyCustomObject>? custom,
}) {
return RawValuesInsertable({
if (notReallyAnId != null) 'not_really_an_id': notReallyAnId,
if (someFloat != null) 'some_float': someFloat,
if (webSafeInt != null) 'web_safe_int': webSafeInt,
if (custom != null) 'custom': custom,
});
}
@ -1080,10 +1085,12 @@ class TableWithoutPKCompanion extends UpdateCompanion<CustomRowClass> {
TableWithoutPKCompanion copyWith(
{Value<int>? notReallyAnId,
Value<double>? someFloat,
Value<BigInt?>? webSafeInt,
Value<MyCustomObject>? custom}) {
return TableWithoutPKCompanion(
notReallyAnId: notReallyAnId ?? this.notReallyAnId,
someFloat: someFloat ?? this.someFloat,
webSafeInt: webSafeInt ?? this.webSafeInt,
custom: custom ?? this.custom,
);
}
@ -1097,6 +1104,9 @@ class TableWithoutPKCompanion extends UpdateCompanion<CustomRowClass> {
if (someFloat.present) {
map['some_float'] = Variable<double>(someFloat.value);
}
if (webSafeInt.present) {
map['web_safe_int'] = Variable<BigInt?>(webSafeInt.value);
}
if (custom.present) {
final converter = $TableWithoutPKTable.$converter0;
map['custom'] = Variable<String>(converter.mapToSql(custom.value)!);
@ -1109,6 +1119,7 @@ class TableWithoutPKCompanion extends UpdateCompanion<CustomRowClass> {
return (StringBuffer('TableWithoutPKCompanion(')
..write('notReallyAnId: $notReallyAnId, ')
..write('someFloat: $someFloat, ')
..write('webSafeInt: $webSafeInt, ')
..write('custom: $custom')
..write(')'))
.toString();
@ -1126,6 +1137,7 @@ class _$CustomRowClassInsertable implements Insertable<CustomRowClass> {
notReallyAnId: Value(_object.notReallyAnId),
someFloat: Value(_object.someFloat),
custom: Value(_object.custom),
webSafeInt: Value(_object.webSafeInt),
).toColumns(false);
}
}
@ -1153,6 +1165,11 @@ class $TableWithoutPKTable extends TableWithoutPK
late final GeneratedColumn<double?> someFloat = GeneratedColumn<double?>(
'some_float', aliasedName, false,
type: const RealType(), requiredDuringInsert: true);
final VerificationMeta _webSafeIntMeta = const VerificationMeta('webSafeInt');
@override
late final GeneratedColumn<BigInt?> webSafeInt = GeneratedColumn<BigInt?>(
'web_safe_int', aliasedName, true,
type: const BigIntType(), requiredDuringInsert: false);
final VerificationMeta _customMeta = const VerificationMeta('custom');
@override
late final GeneratedColumnWithTypeConverter<MyCustomObject, String?> custom =
@ -1162,7 +1179,8 @@ class $TableWithoutPKTable extends TableWithoutPK
clientDefault: _uuid.v4)
.withConverter<MyCustomObject>($TableWithoutPKTable.$converter0);
@override
List<GeneratedColumn> get $columns => [notReallyAnId, someFloat, custom];
List<GeneratedColumn> get $columns =>
[notReallyAnId, someFloat, webSafeInt, custom];
@override
String get aliasedName => _alias ?? 'table_without_p_k';
@override
@ -1186,6 +1204,12 @@ class $TableWithoutPKTable extends TableWithoutPK
} else if (isInserting) {
context.missing(_someFloatMeta);
}
if (data.containsKey('web_safe_int')) {
context.handle(
_webSafeIntMeta,
webSafeInt.isAcceptableOrUnknown(
data['web_safe_int']!, _webSafeIntMeta));
}
context.handle(_customMeta, const VerificationResult.success());
return context;
}
@ -1202,6 +1226,8 @@ class $TableWithoutPKTable extends TableWithoutPK
.mapFromDatabaseResponse(data['${effectivePrefix}some_float'])!,
custom: $TableWithoutPKTable.$converter0.mapToDart(const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}custom']))!,
webSafeInt: const BigIntType()
.mapFromDatabaseResponse(data['${effectivePrefix}web_safe_int']),
);
}

View File

@ -136,4 +136,14 @@ void main() {
),
completes);
});
test('insert and select BigInt', () async {
await db.into(db.tableWithoutPK).insert(CustomRowClass.map(1, 0.0,
webSafeInt: BigInt.parse('9223372036854775807'),
custom: MyCustomObject('custom'))
.toInsertable());
final row = await db.select(db.tableWithoutPK).getSingle();
expect(row.webSafeInt, BigInt.parse('9223372036854775807'));
});
}