mirror of https://github.com/AMT-Cheif/drift.git
Implement delete
This commit is contained in:
parent
64ceb2acfa
commit
2a69573330
|
@ -21,6 +21,6 @@ class Users extends Table {
|
||||||
class ShopDb extends SallyDb with _$ShopDbMixin {
|
class ShopDb extends SallyDb with _$ShopDbMixin {
|
||||||
|
|
||||||
Future<List<User>> allUsers() => users.select().get();
|
Future<List<User>> allUsers() => users.select().get();
|
||||||
Future<User> userByName(String name) => users.select().where((u) => u.name.equals(name)).single();
|
Future<User> userByName(String name) => (users.select()..where((u) => u.name.equals(name))).single();
|
||||||
|
|
||||||
}
|
}
|
|
@ -8,6 +8,10 @@ class _$ShopDbMixin implements QueryExecutor {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<int> executeDelete(String sql, [dynamic params]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class StructuredUsersTable extends Users with TableStructure<Users, User> {
|
class StructuredUsersTable extends Users with TableStructure<Users, User> {
|
||||||
|
|
|
@ -7,6 +7,7 @@ class UseData {
|
||||||
|
|
||||||
abstract class QueryExecutor {
|
abstract class QueryExecutor {
|
||||||
Future<List<Map<String, dynamic>>> executeQuery(String sql, [dynamic params]);
|
Future<List<Map<String, dynamic>>> executeQuery(String sql, [dynamic params]);
|
||||||
|
Future<int> executeDelete(String sql, [dynamic params]);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class SallyDb {}
|
abstract class SallyDb {}
|
||||||
|
|
|
@ -7,18 +7,18 @@ class Column<T> {
|
||||||
Predicate equals(T compare) => null;
|
Predicate equals(T compare) => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class IntColumn extends Column<int> {
|
abstract class IntColumn extends Column<int> {
|
||||||
Predicate isBiggerThan(int i) => null;
|
Predicate isBiggerThan(int i);
|
||||||
Predicate isSmallerThan(int i) => null;
|
Predicate isSmallerThan(int i);
|
||||||
}
|
}
|
||||||
|
|
||||||
class BoolColumn extends Column<bool> {
|
abstract class BoolColumn extends Column<bool> {
|
||||||
Predicate isTrue() => null;
|
Predicate isTrue();
|
||||||
Predicate isFalse() => null;
|
Predicate isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextColumn extends Column<String> {
|
abstract class TextColumn extends Column<String> {
|
||||||
Predicate like(String regex) => null;
|
Predicate like(String regex);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ColumnBuilder<T> {
|
class ColumnBuilder<T> {
|
||||||
|
|
|
@ -13,3 +13,16 @@ class Variable extends SqlExpression {
|
||||||
context.buffer.write('? ');
|
context.buffer.write('? ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HardcodedConstant extends SqlExpression {
|
||||||
|
|
||||||
|
final dynamic value;
|
||||||
|
|
||||||
|
HardcodedConstant(this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeInto(GenerationContext context) {
|
||||||
|
context.buffer.write(context.harcodedSqlValue(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -5,4 +5,8 @@ class GenerationContext {
|
||||||
void addBoundVariable(dynamic data) {
|
void addBoundVariable(dynamic data) {
|
||||||
boundVariables.add(data);
|
boundVariables.add(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String harcodedSqlValue(dynamic value) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:sally/src/queries/expressions/expressions.dart';
|
||||||
|
import 'package:sally/src/queries/statement/statements.dart';
|
||||||
|
import 'package:sally/src/queries/table_structure.dart';
|
||||||
|
|
||||||
|
class DeleteStatement<Table> with Limitable, WhereFilterable<Table, dynamic> {
|
||||||
|
|
||||||
|
DeleteStatement(TableStructure<Table, dynamic> table) {
|
||||||
|
super.table = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes all records matched by the optional where and limit statements.
|
||||||
|
/// Returns the amount of deleted rows.
|
||||||
|
Future<int> performDelete() {
|
||||||
|
GenerationContext context = GenerationContext();
|
||||||
|
context.buffer.write('DELETE FROM ');
|
||||||
|
context.buffer.write(table.sqlTableName);
|
||||||
|
context.buffer.write(' ');
|
||||||
|
|
||||||
|
if (hasWhere) whereExpression.writeInto(context);
|
||||||
|
if (hasLimit) limitExpression.writeInto(context);
|
||||||
|
|
||||||
|
return table.executor.executeDelete(context.buffer.toString(), context.boundVariables);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
import 'package:sally/src/queries/expressions/expressions.dart';
|
||||||
|
import 'package:sally/src/queries/statement/statements.dart';
|
||||||
|
import 'package:sally/src/queries/table_structure.dart';
|
||||||
|
|
||||||
|
class SelectStatement<Table, Result> with Limitable, WhereFilterable<Table, Result> {
|
||||||
|
|
||||||
|
SelectStatement(TableStructure<Table, Result> table) {
|
||||||
|
super.table = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
GenerationContext _buildQuery() {
|
||||||
|
GenerationContext context = GenerationContext();
|
||||||
|
context.buffer.write('SELECT * FROM ');
|
||||||
|
context.buffer.write(table.sqlTableName);
|
||||||
|
context.buffer.write(' ');
|
||||||
|
|
||||||
|
if (hasWhere) whereExpression.writeInto(context);
|
||||||
|
if (hasLimit) limitExpression.writeInto(context);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes the select statement on the database and maps the returned rows
|
||||||
|
/// to the right dataclass.
|
||||||
|
Future<List<Result>> get() async {
|
||||||
|
final ctx = _buildQuery();
|
||||||
|
final sql = ctx.buffer.toString();
|
||||||
|
final vars = ctx.boundVariables;
|
||||||
|
|
||||||
|
final result = await table.executor.executeQuery(sql, vars);
|
||||||
|
return result.map(table.parse).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to [get], but it will only load one item by setting [limit()]
|
||||||
|
/// appropriately. This method will throw if no results where found. If you're
|
||||||
|
/// ok with no result existing, try [singleOrNull] instead.
|
||||||
|
Future<Result> single() async {
|
||||||
|
final element = singleOrNull();
|
||||||
|
if (element == null)
|
||||||
|
throw StateError("No item was returned by the query called with single()");
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to [get], but only uses one row of the result by setting the limit
|
||||||
|
/// accordingly. If no item was found, null will be returned instead.
|
||||||
|
Future<Result> singleOrNull() async {
|
||||||
|
// limit to one item, using the existing offset if it exists
|
||||||
|
limitExpression = LimitExpression(1, limitExpression.offset ?? 0);
|
||||||
|
|
||||||
|
final results = await get();
|
||||||
|
if (results.isEmpty)
|
||||||
|
return null;
|
||||||
|
return results.single;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:sally/src/queries/expressions/expressions.dart';
|
||||||
|
import 'package:sally/src/queries/expressions/limit.dart';
|
||||||
|
import 'package:sally/src/queries/expressions/where.dart';
|
||||||
|
import 'package:sally/src/queries/predicates/predicate.dart';
|
||||||
|
import 'package:sally/src/queries/table_structure.dart';
|
||||||
|
|
||||||
|
/// Mixin for statements that allow a LIMIT operator
|
||||||
|
class Limitable {
|
||||||
|
|
||||||
|
@protected
|
||||||
|
LimitExpression limitExpression;
|
||||||
|
|
||||||
|
void limit({int amount, int offset}) {
|
||||||
|
limitExpression = LimitExpression(amount, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
bool get hasLimit => limitExpression != null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mixin for statements that allow a WHERE operator on a specific table.
|
||||||
|
class WhereFilterable<Table, Result> {
|
||||||
|
|
||||||
|
@protected
|
||||||
|
TableStructure<Table, Result> table;
|
||||||
|
@protected
|
||||||
|
WhereExpression whereExpression;
|
||||||
|
|
||||||
|
bool get hasWhere => whereExpression != null;
|
||||||
|
|
||||||
|
void where(Predicate filter(Table table)) {
|
||||||
|
final addedPredicate = filter(table.asTable);
|
||||||
|
|
||||||
|
if (hasWhere) {
|
||||||
|
// merge existing where expression together with new one by and-ing them
|
||||||
|
// together.
|
||||||
|
whereExpression = WhereExpression(whereExpression.predicate.and(addedPredicate));
|
||||||
|
} else {
|
||||||
|
whereExpression = WhereExpression(addedPredicate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,71 +0,0 @@
|
||||||
import 'package:sally/src/queries/expressions/limit.dart';
|
|
||||||
import 'package:sally/src/queries/expressions/where.dart';
|
|
||||||
import 'package:sally/src/queries/generation_context.dart';
|
|
||||||
import 'package:sally/src/queries/predicates/predicate.dart';
|
|
||||||
import 'package:sally/src/queries/table_structure.dart';
|
|
||||||
|
|
||||||
abstract class SqlStatement {
|
|
||||||
GenerationContext _buildQuery();
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class StatementForExistingData<Table, Result> extends SqlStatement {
|
|
||||||
final TableStructure<Table, Result> _table;
|
|
||||||
|
|
||||||
StatementForExistingData(this._table);
|
|
||||||
|
|
||||||
WhereExpression _where;
|
|
||||||
LimitExpression _limit;
|
|
||||||
|
|
||||||
Future<List<Result>> get() async {
|
|
||||||
final ctx = _buildQuery();
|
|
||||||
final sql = ctx.buffer.toString();
|
|
||||||
final vars = ctx.boundVariables;
|
|
||||||
|
|
||||||
final result = await _table.executor.executeQuery(sql, vars);
|
|
||||||
return result.map(_table.parse).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Result> single() async {
|
|
||||||
// limit to one item, using the existing offset if it exists
|
|
||||||
_limit = LimitExpression(1, _limit?.offset ?? 0);
|
|
||||||
|
|
||||||
return (await get()).single;
|
|
||||||
}
|
|
||||||
|
|
||||||
StatementForExistingData<Table, Result> where(
|
|
||||||
Predicate extractor(Table tbl)) {
|
|
||||||
final addedPredicate = extractor(_table.asTable);
|
|
||||||
|
|
||||||
if (_where != null) {
|
|
||||||
// merge existing where expression together with new one by and-ing them
|
|
||||||
// together.
|
|
||||||
_where = WhereExpression(_where.predicate.and(addedPredicate));
|
|
||||||
} else {
|
|
||||||
_where = WhereExpression(addedPredicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
StatementForExistingData<Table, Result> limit({int amount, int offset}) {
|
|
||||||
_limit = LimitExpression(amount, offset);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SelectStatement<T, R> extends StatementForExistingData<T, R> {
|
|
||||||
SelectStatement(TableStructure<T, R> table) : super(table);
|
|
||||||
|
|
||||||
@override
|
|
||||||
GenerationContext _buildQuery() {
|
|
||||||
GenerationContext context = GenerationContext();
|
|
||||||
context.buffer.write('SELECT * FROM ');
|
|
||||||
context.buffer.write(_table.sqlTableName);
|
|
||||||
context.buffer.write(' ');
|
|
||||||
|
|
||||||
if (_where != null) _where.writeInto(context);
|
|
||||||
if (_limit != null) _limit.writeInto(context);
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,7 +6,8 @@ import 'package:sally/src/queries/generation_context.dart';
|
||||||
import 'package:sally/src/queries/predicates/numbers.dart';
|
import 'package:sally/src/queries/predicates/numbers.dart';
|
||||||
import 'package:sally/src/queries/predicates/predicate.dart';
|
import 'package:sally/src/queries/predicates/predicate.dart';
|
||||||
import 'package:sally/src/queries/predicates/text.dart';
|
import 'package:sally/src/queries/predicates/text.dart';
|
||||||
import 'package:sally/src/queries/statements.dart';
|
import 'package:sally/src/queries/statement/delete.dart';
|
||||||
|
import 'package:sally/src/queries/statement/select.dart';
|
||||||
|
|
||||||
abstract class TableStructure<UserSpecifiedTable, ResolvedType> {
|
abstract class TableStructure<UserSpecifiedTable, ResolvedType> {
|
||||||
QueryExecutor executor;
|
QueryExecutor executor;
|
||||||
|
@ -18,6 +19,8 @@ abstract class TableStructure<UserSpecifiedTable, ResolvedType> {
|
||||||
|
|
||||||
SelectStatement<UserSpecifiedTable, ResolvedType> select() =>
|
SelectStatement<UserSpecifiedTable, ResolvedType> select() =>
|
||||||
SelectStatement<UserSpecifiedTable, ResolvedType>(this);
|
SelectStatement<UserSpecifiedTable, ResolvedType>(this);
|
||||||
|
|
||||||
|
DeleteStatement<UserSpecifiedTable> delete() => DeleteStatement(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
class StructuredColumn<T> implements SqlExpression, Column<T> {
|
class StructuredColumn<T> implements SqlExpression, Column<T> {
|
||||||
|
@ -51,14 +54,15 @@ class StructuredBoolColumn extends StructuredColumn<bool>
|
||||||
implements BoolColumn {
|
implements BoolColumn {
|
||||||
StructuredBoolColumn(String sqlName) : super(sqlName);
|
StructuredBoolColumn(String sqlName) : super(sqlName);
|
||||||
|
|
||||||
@override
|
// Booleans will be stored as integers, where 0 means false and 1 means true
|
||||||
Predicate isFalse() {
|
|
||||||
return not(isTrue());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Predicate isFalse() {
|
||||||
|
return EqualityPredicate(this, HardcodedConstant(0));
|
||||||
|
}
|
||||||
@override
|
@override
|
||||||
Predicate isTrue() {
|
Predicate isTrue() {
|
||||||
return BooleanExpressionPredicate(this);
|
return EqualityPredicate(this, HardcodedConstant(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,25 +56,40 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("generates limit statements", () {
|
test("generates limit statements", () {
|
||||||
users.select().limit(amount: 10).get();
|
(users.select()..limit(amount: 10)).get();
|
||||||
verify(executor.executeQuery("SELECT * FROM users LIMIT 10 ", any));
|
verify(executor.executeQuery("SELECT * FROM users LIMIT 10 ", any));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("generates like expressions", () {
|
test("generates like expressions", () {
|
||||||
users.select().where((u) => u.name.like("Dash%")).get();
|
(users.select()..where((u) => u.name.like("Dash%"))).get();
|
||||||
verify(executor
|
verify(executor
|
||||||
.executeQuery("SELECT * FROM users WHERE name LIKE ? ", ["Dash%"]));
|
.executeQuery("SELECT * FROM users WHERE name LIKE ? ", ["Dash%"]));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("generates complex predicates", () {
|
test("generates complex predicates", () {
|
||||||
users
|
(users.select()
|
||||||
.select()
|
..where(
|
||||||
.where((u) => not(u.name.equals("Dash")).and(u.id.isBiggerThan(12)))
|
(u) => not(u.name.equals("Dash")).and(u.id.isBiggerThan(12))))
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
verify(executor.executeQuery(
|
verify(executor.executeQuery(
|
||||||
"SELECT * FROM users WHERE (NOT name = ? ) AND (id > ? ) ",
|
"SELECT * FROM users WHERE (NOT name = ? ) AND (id > ? ) ",
|
||||||
["Dash", 12]));
|
["Dash", 12]));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("generates expressions from boolean fields", () {
|
||||||
|
(users.select()..where((u) => u.isAwesome.isTrue())).get();
|
||||||
|
|
||||||
|
verify(executor.executeQuery(
|
||||||
|
"SELECT * FROM users WHERE is_awesome = 1", any));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group("Generates DELETE statements", () {
|
||||||
|
test("without any constaints", () {
|
||||||
|
users.delete().performDelete();
|
||||||
|
|
||||||
|
verify(executor.executeDelete("DELETE FROM users ", argThat(isEmpty)));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue