mirror of https://github.com/AMT-Cheif/drift.git
Redo runtime api with regards to type safety and fun
This commit is contained in:
parent
2a69573330
commit
9090ded541
|
@ -1,5 +1,4 @@
|
||||||
import 'package:sally/sally.dart';
|
import 'package:sally/sally.dart';
|
||||||
import 'package:sally/src/queries/table_structure.dart';
|
|
||||||
|
|
||||||
part 'example.g.dart';
|
part 'example.g.dart';
|
||||||
|
|
||||||
|
@ -18,9 +17,10 @@ class Users extends Table {
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseData(tables: [Products, Users])
|
@UseData(tables: [Products, Users])
|
||||||
class ShopDb extends SallyDb with _$ShopDbMixin {
|
class ShopDb extends _$ShopDb {
|
||||||
|
ShopDb(SqlTypeSystem typeSystem, QueryExecutor executor) : super(typeSystem, executor);
|
||||||
|
|
||||||
Future<List<User>> allUsers() => users.select().get();
|
Future<List<User>> allUsers() => select(users).get();
|
||||||
Future<User> userByName(String name) => (users.select()..where((u) => u.name.equals(name))).single();
|
Future<List<User>> userByName(String name) => (select(users)..where((u) => u.name.equalsVal(name))).get();
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,37 +1,10 @@
|
||||||
part of 'example.dart';
|
part of 'example.dart';
|
||||||
|
|
||||||
class _$ShopDbMixin implements QueryExecutor {
|
class _$ShopDb extends GeneratedDatabase {
|
||||||
|
|
||||||
final StructuredUsersTable users = StructuredUsersTable();
|
_$ShopDb(SqlTypeSystem typeSystem, QueryExecutor executor) : super(typeSystem, executor);
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>> executeQuery(String sql, [dynamic params]) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> executeDelete(String sql, [dynamic params]) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class StructuredUsersTable extends Users with TableStructure<Users, User> {
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
final StructuredIntColumn id = StructuredIntColumn("id");
|
|
||||||
@override
|
|
||||||
final StructuredTextColumn name = StructuredTextColumn("name");
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get sqlTableName => "users";
|
|
||||||
|
|
||||||
@override
|
|
||||||
User parse(Map<String, dynamic> result) {
|
|
||||||
return User(result["id"], result["name"]);
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
Users get asTable => this;
|
|
||||||
|
|
||||||
|
UsersTable get users => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class User {
|
class User {
|
||||||
|
@ -41,4 +14,35 @@ class User {
|
||||||
|
|
||||||
User(this.id, this.name);
|
User(this.id, this.name);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UsersTable extends Users implements TableInfo<Users, User> {
|
||||||
|
|
||||||
|
final GeneratedDatabase db;
|
||||||
|
|
||||||
|
UsersTable(this.db);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Column> get $columns => [id, name];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get $tableName => "users";
|
||||||
|
|
||||||
|
@override
|
||||||
|
IntColumn get id => GeneratedIntColumn("id");
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextColumn get name => GeneratedTextColumn("name");
|
||||||
|
|
||||||
|
@override
|
||||||
|
Users get asDslTable => this;
|
||||||
|
|
||||||
|
@override
|
||||||
|
User map(Map<String, dynamic> data) {
|
||||||
|
final intType = db.typeSystem.forDartType<int>();
|
||||||
|
final stringType = db.typeSystem.forDartType<String>();
|
||||||
|
|
||||||
|
return User(intType.mapFromDatabaseResponse(data["id"]), stringType.mapFromDatabaseResponse(data["name"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -2,4 +2,13 @@ library sally;
|
||||||
|
|
||||||
export 'package:sally/src/dsl/table.dart';
|
export 'package:sally/src/dsl/table.dart';
|
||||||
export 'package:sally/src/dsl/columns.dart';
|
export 'package:sally/src/dsl/columns.dart';
|
||||||
export 'package:sally/src/database.dart';
|
export 'package:sally/src/dsl/database.dart';
|
||||||
|
|
||||||
|
export 'package:sally/src/runtime/executor/executor.dart';
|
||||||
|
export 'package:sally/src/runtime/executor/type_system.dart';
|
||||||
|
export 'package:sally/src/runtime/expressions/user_api.dart';
|
||||||
|
export 'package:sally/src/runtime/statements/query.dart';
|
||||||
|
export 'package:sally/src/runtime/statements/select.dart';
|
||||||
|
export 'package:sally/src/runtime/structure/columns.dart';
|
||||||
|
export 'package:sally/src/runtime/structure/table_info.dart';
|
||||||
|
export 'package:sally/src/runtime/sql_types.dart';
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
class UseData {
|
|
||||||
final List<Type> tables;
|
|
||||||
final int schemaVersion;
|
|
||||||
|
|
||||||
const UseData({this.tables, this.schemaVersion = 1});
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class QueryExecutor {
|
|
||||||
Future<List<Map<String, dynamic>>> executeQuery(String sql, [dynamic params]);
|
|
||||||
Future<int> executeDelete(String sql, [dynamic params]);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class SallyDb {}
|
|
|
@ -1,24 +1,23 @@
|
||||||
// todo more datatypes (at least DateTime and Binary blobs)!
|
// todo more datatypes (at least DateTime and Binary blobs)!
|
||||||
// todo nullability
|
// todo nullability
|
||||||
|
|
||||||
import 'package:sally/src/queries/predicates/predicate.dart';
|
import 'package:sally/src/runtime/expressions/expression.dart';
|
||||||
|
import 'package:sally/src/runtime/sql_types.dart';
|
||||||
|
|
||||||
class Column<T> {
|
abstract class Column<T, S extends SqlType<T>> extends Expression<S> {
|
||||||
Predicate equals(T compare) => null;
|
Expression<BoolType> equals(Expression<S> compare);
|
||||||
|
Expression<BoolType> equalsVal(T compare);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class IntColumn extends Column<int> {
|
abstract class IntColumn extends Column<int, IntType> {
|
||||||
Predicate isBiggerThan(int i);
|
Expression<BoolType> isBiggerThan(int i);
|
||||||
Predicate isSmallerThan(int i);
|
Expression<BoolType> isSmallerThan(int i);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class BoolColumn extends Column<bool> {
|
abstract class BoolColumn extends Column<bool, BoolType> {}
|
||||||
Predicate isTrue();
|
|
||||||
Predicate isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class TextColumn extends Column<String> {
|
abstract class TextColumn extends Column<String, StringType> {
|
||||||
Predicate like(String regex);
|
Expression<BoolType> like(String regex);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ColumnBuilder<T> {
|
class ColumnBuilder<T> {
|
||||||
|
@ -29,7 +28,7 @@ class ColumnBuilder<T> {
|
||||||
ColumnBuilder<T> primaryKey() => this;
|
ColumnBuilder<T> primaryKey() => this;
|
||||||
// ColumnBuilder<T> references<Table>(Column<T> extractor(Table table)) => this;
|
// ColumnBuilder<T> references<Table>(Column<T> extractor(Table table)) => this;
|
||||||
|
|
||||||
Column<T> call() => null;
|
Column<T, dynamic> call() => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class IntColumnBuilder extends ColumnBuilder<int> {
|
class IntColumnBuilder extends ColumnBuilder<int> {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
class UseData {
|
||||||
|
final List<Type> tables;
|
||||||
|
final int schemaVersion;
|
||||||
|
|
||||||
|
const UseData({this.tables, this.schemaVersion = 1});
|
||||||
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
export 'package:sally/src/queries/generation_context.dart';
|
|
||||||
export 'package:sally/src/queries/expressions/limit.dart';
|
|
||||||
export 'package:sally/src/queries/expressions/variable.dart';
|
|
||||||
export 'package:sally/src/queries/expressions/where.dart';
|
|
||||||
|
|
||||||
import 'package:sally/src/queries/expressions/expressions.dart';
|
|
||||||
|
|
||||||
abstract class SqlExpression {
|
|
||||||
void writeInto(GenerationContext context);
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import 'package:sally/src/queries/expressions/expressions.dart';
|
|
||||||
import 'package:sally/src/queries/generation_context.dart';
|
|
||||||
|
|
||||||
class LimitExpression extends SqlExpression {
|
|
||||||
final int amount;
|
|
||||||
final int offset;
|
|
||||||
|
|
||||||
LimitExpression(this.amount, this.offset);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void writeInto(GenerationContext context) {
|
|
||||||
if (offset != null)
|
|
||||||
context.buffer.write('LIMIT $amount, $offset ');
|
|
||||||
else
|
|
||||||
context.buffer.write('LIMIT $amount ');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
import 'package:sally/src/queries/expressions/expressions.dart';
|
|
||||||
import 'package:sally/src/queries/generation_context.dart';
|
|
||||||
|
|
||||||
class Variable extends SqlExpression {
|
|
||||||
final dynamic value;
|
|
||||||
|
|
||||||
Variable(this.value);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void writeInto(GenerationContext context) {
|
|
||||||
context.addBoundVariable(value);
|
|
||||||
|
|
||||||
context.buffer.write('? ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HardcodedConstant extends SqlExpression {
|
|
||||||
|
|
||||||
final dynamic value;
|
|
||||||
|
|
||||||
HardcodedConstant(this.value);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void writeInto(GenerationContext context) {
|
|
||||||
context.buffer.write(context.harcodedSqlValue(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import 'package:sally/src/queries/expressions/expressions.dart';
|
|
||||||
import 'package:sally/src/queries/generation_context.dart';
|
|
||||||
import 'package:sally/src/queries/predicates/predicate.dart';
|
|
||||||
|
|
||||||
class WhereExpression extends SqlExpression {
|
|
||||||
final Predicate predicate;
|
|
||||||
|
|
||||||
WhereExpression(this.predicate);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void writeInto(GenerationContext context) {
|
|
||||||
context.buffer.write("WHERE ");
|
|
||||||
predicate.writeInto(context);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
class GenerationContext {
|
|
||||||
StringBuffer buffer = StringBuffer();
|
|
||||||
List<dynamic> boundVariables = List();
|
|
||||||
|
|
||||||
void addBoundVariable(dynamic data) {
|
|
||||||
boundVariables.add(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
String harcodedSqlValue(dynamic value) {
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
import 'package:sally/src/queries/generation_context.dart';
|
|
||||||
import 'package:sally/src/queries/predicates/predicate.dart';
|
|
||||||
|
|
||||||
class NotPredicate extends Predicate {
|
|
||||||
final Predicate inner;
|
|
||||||
|
|
||||||
NotPredicate(this.inner);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void writeInto(GenerationContext context) {
|
|
||||||
context.buffer.write("NOT ");
|
|
||||||
inner.writeInto(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class OrPredicate extends Predicate {
|
|
||||||
final Predicate a, b;
|
|
||||||
|
|
||||||
OrPredicate(this.a, this.b);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void writeInto(GenerationContext context) {
|
|
||||||
context.buffer.write('(');
|
|
||||||
a.writeInto(context);
|
|
||||||
context.buffer.write(') OR ( ');
|
|
||||||
b.writeInto(context);
|
|
||||||
context.buffer.write(') ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AndPredicate extends Predicate {
|
|
||||||
final Predicate a, b;
|
|
||||||
|
|
||||||
AndPredicate(this.a, this.b);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void writeInto(GenerationContext context) {
|
|
||||||
context.buffer.write('(');
|
|
||||||
a.writeInto(context);
|
|
||||||
context.buffer.write(') AND (');
|
|
||||||
b.writeInto(context);
|
|
||||||
context.buffer.write(') ');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
import 'package:sally/src/queries/expressions/expressions.dart';
|
|
||||||
import 'package:sally/src/queries/generation_context.dart';
|
|
||||||
import 'package:sally/src/queries/predicates/predicate.dart';
|
|
||||||
|
|
||||||
enum ComparisonOperator { less, less_or_equal, more, more_or_equal }
|
|
||||||
|
|
||||||
class NumberComparisonPredicate extends Predicate {
|
|
||||||
static const Map<ComparisonOperator, String> _operators = {
|
|
||||||
ComparisonOperator.less: '< ',
|
|
||||||
ComparisonOperator.less_or_equal: '<= ',
|
|
||||||
ComparisonOperator.more: '> ',
|
|
||||||
ComparisonOperator.more_or_equal: '>= ',
|
|
||||||
};
|
|
||||||
|
|
||||||
SqlExpression left;
|
|
||||||
ComparisonOperator operator;
|
|
||||||
SqlExpression right;
|
|
||||||
|
|
||||||
NumberComparisonPredicate(this.left, this.operator, this.right);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void writeInto(GenerationContext context) {
|
|
||||||
left.writeInto(context);
|
|
||||||
context.buffer.write(_operators[operator]);
|
|
||||||
right.writeInto(context);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
export 'package:sally/src/queries/predicates/combining.dart';
|
|
||||||
export 'package:sally/src/queries/predicates/numbers.dart';
|
|
||||||
export 'package:sally/src/queries/predicates/text.dart';
|
|
||||||
|
|
||||||
import 'package:sally/src/queries/expressions/expressions.dart';
|
|
||||||
import 'package:sally/src/queries/generation_context.dart';
|
|
||||||
import 'package:sally/src/queries/predicates/combining.dart';
|
|
||||||
|
|
||||||
Predicate not(Predicate p) => p.not();
|
|
||||||
|
|
||||||
abstract class Predicate extends SqlExpression {
|
|
||||||
Predicate not() {
|
|
||||||
return NotPredicate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate and(Predicate other) => AndPredicate(this, other);
|
|
||||||
Predicate or(Predicate other) => OrPredicate(this, other);
|
|
||||||
}
|
|
||||||
|
|
||||||
class EqualityPredicate extends Predicate {
|
|
||||||
SqlExpression left;
|
|
||||||
SqlExpression right;
|
|
||||||
|
|
||||||
EqualityPredicate(this.left, this.right);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void writeInto(GenerationContext context) {
|
|
||||||
left.writeInto(context);
|
|
||||||
context.buffer.write('= ');
|
|
||||||
right.writeInto(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BooleanExpressionPredicate extends Predicate {
|
|
||||||
SqlExpression expression;
|
|
||||||
|
|
||||||
BooleanExpressionPredicate(this.expression);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void writeInto(GenerationContext context) {
|
|
||||||
expression.writeInto(context);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import 'package:sally/src/queries/expressions/expressions.dart';
|
|
||||||
import 'package:sally/src/queries/generation_context.dart';
|
|
||||||
import 'package:sally/src/queries/predicates/predicate.dart';
|
|
||||||
|
|
||||||
class LikePredicate extends Predicate {
|
|
||||||
SqlExpression target;
|
|
||||||
SqlExpression regex;
|
|
||||||
|
|
||||||
LikePredicate(this.target, this.regex);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void writeInto(GenerationContext context) {
|
|
||||||
target.writeInto(context);
|
|
||||||
context.buffer.write('LIKE ');
|
|
||||||
regex.writeInto(context);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
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,75 +0,0 @@
|
||||||
import 'package:sally/sally.dart';
|
|
||||||
import 'package:sally/src/dsl/columns.dart';
|
|
||||||
import 'package:sally/src/queries/expressions/expressions.dart';
|
|
||||||
import 'package:sally/src/queries/expressions/variable.dart';
|
|
||||||
import 'package:sally/src/queries/generation_context.dart';
|
|
||||||
import 'package:sally/src/queries/predicates/numbers.dart';
|
|
||||||
import 'package:sally/src/queries/predicates/predicate.dart';
|
|
||||||
import 'package:sally/src/queries/predicates/text.dart';
|
|
||||||
import 'package:sally/src/queries/statement/delete.dart';
|
|
||||||
import 'package:sally/src/queries/statement/select.dart';
|
|
||||||
|
|
||||||
abstract class TableStructure<UserSpecifiedTable, ResolvedType> {
|
|
||||||
QueryExecutor executor;
|
|
||||||
|
|
||||||
UserSpecifiedTable get asTable;
|
|
||||||
String get sqlTableName;
|
|
||||||
|
|
||||||
ResolvedType parse(Map<String, dynamic> result);
|
|
||||||
|
|
||||||
SelectStatement<UserSpecifiedTable, ResolvedType> select() =>
|
|
||||||
SelectStatement<UserSpecifiedTable, ResolvedType>(this);
|
|
||||||
|
|
||||||
DeleteStatement<UserSpecifiedTable> delete() => DeleteStatement(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
class StructuredColumn<T> implements SqlExpression, Column<T> {
|
|
||||||
final String sqlName;
|
|
||||||
|
|
||||||
StructuredColumn(this.sqlName);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void writeInto(GenerationContext context) {
|
|
||||||
// todo table name lookup, as-expressions etc?
|
|
||||||
context.buffer.write(sqlName);
|
|
||||||
context.buffer.write(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Predicate equals(T compare) => EqualityPredicate(this, Variable(compare));
|
|
||||||
}
|
|
||||||
|
|
||||||
class StructuredIntColumn extends StructuredColumn<int> implements IntColumn {
|
|
||||||
StructuredIntColumn(String sqlName) : super(sqlName);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Predicate isBiggerThan(int i) =>
|
|
||||||
NumberComparisonPredicate(this, ComparisonOperator.more, Variable(i));
|
|
||||||
@override
|
|
||||||
Predicate isSmallerThan(int i) =>
|
|
||||||
NumberComparisonPredicate(this, ComparisonOperator.less, Variable(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
class StructuredBoolColumn extends StructuredColumn<bool>
|
|
||||||
implements BoolColumn {
|
|
||||||
StructuredBoolColumn(String sqlName) : super(sqlName);
|
|
||||||
|
|
||||||
// Booleans will be stored as integers, where 0 means false and 1 means true
|
|
||||||
|
|
||||||
@override
|
|
||||||
Predicate isFalse() {
|
|
||||||
return EqualityPredicate(this, HardcodedConstant(0));
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
Predicate isTrue() {
|
|
||||||
return EqualityPredicate(this, HardcodedConstant(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class StructuredTextColumn extends StructuredColumn<String>
|
|
||||||
implements TextColumn {
|
|
||||||
StructuredTextColumn(String sqlName) : super(sqlName);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Predicate like(String regex) => LikePredicate(this, Variable(regex));
|
|
||||||
}
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import 'package:sally/src/runtime/executor/executor.dart';
|
||||||
|
|
||||||
|
/// Anything that can appear in a sql query.
|
||||||
|
abstract class Component {
|
||||||
|
/// Writes this component into the [context] by writing to its
|
||||||
|
/// [GenerationContext.buffer] or by introducing bound variables.
|
||||||
|
void writeInto(GenerationContext context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains information about a query while it's being constructed.
|
||||||
|
class GenerationContext {
|
||||||
|
final GeneratedDatabase database;
|
||||||
|
|
||||||
|
final List<dynamic> _boundVariables = [];
|
||||||
|
List<dynamic> get boundVariables => _boundVariables;
|
||||||
|
|
||||||
|
final StringBuffer buffer = StringBuffer();
|
||||||
|
|
||||||
|
String get sql => buffer.toString();
|
||||||
|
|
||||||
|
GenerationContext(this.database);
|
||||||
|
|
||||||
|
void introduceVariable(dynamic value) {
|
||||||
|
_boundVariables.add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeWhitespace() => buffer.write(' ');
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import 'package:sally/src/runtime/components/component.dart';
|
||||||
|
|
||||||
|
class Limit extends Component {
|
||||||
|
final int amount;
|
||||||
|
final int offset;
|
||||||
|
|
||||||
|
Limit(this.amount, this.offset);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeInto(GenerationContext context) {
|
||||||
|
if (offset != null)
|
||||||
|
context.buffer.write('LIMIT $amount, $offset');
|
||||||
|
else
|
||||||
|
context.buffer.write('LIMIT $amount');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:sally/src/runtime/components/component.dart';
|
||||||
|
import 'package:sally/src/runtime/expressions/expression.dart';
|
||||||
|
import 'package:sally/src/runtime/sql_types.dart';
|
||||||
|
|
||||||
|
class Where extends Component {
|
||||||
|
final Expression<BoolType> predicate;
|
||||||
|
|
||||||
|
Where(this.predicate);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeInto(GenerationContext context) {
|
||||||
|
context.buffer.write("WHERE ");
|
||||||
|
predicate.writeInto(context);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:sally/sally.dart';
|
||||||
|
import 'package:sally/src/runtime/executor/type_system.dart';
|
||||||
|
import 'package:sally/src/runtime/statements/select.dart';
|
||||||
|
|
||||||
|
/// A base class for all generated databases.
|
||||||
|
abstract class GeneratedDatabase {
|
||||||
|
final SqlTypeSystem typeSystem;
|
||||||
|
final QueryExecutor executor;
|
||||||
|
|
||||||
|
GeneratedDatabase(this.typeSystem, this.executor);
|
||||||
|
|
||||||
|
SelectStatement<Table, ReturnType> select<Table, ReturnType>(
|
||||||
|
TableInfo<Table, ReturnType> table) {
|
||||||
|
return SelectStatement<Table, ReturnType>(this, table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class QueryExecutor {
|
||||||
|
Future<bool> ensureOpen();
|
||||||
|
Future<List<Map<String, dynamic>>> runSelect(
|
||||||
|
String statement, List<dynamic> args);
|
||||||
|
List<int> runCreate(String statement, List<dynamic> args);
|
||||||
|
Future<int> runUpdate(String statement, List<dynamic> args);
|
||||||
|
Future<int> runDelete(String statement, List<dynamic> args);
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import 'package:sally/src/runtime/sql_types.dart';
|
||||||
|
|
||||||
|
class SqlTypeSystem {
|
||||||
|
final List<SqlType> types;
|
||||||
|
|
||||||
|
const SqlTypeSystem(this.types);
|
||||||
|
|
||||||
|
const SqlTypeSystem.withDefaults()
|
||||||
|
: this(const [BoolType(), StringType(), IntType()]);
|
||||||
|
|
||||||
|
/// Returns the appropriate sql type for the dart type provided as the
|
||||||
|
/// generic parameter.
|
||||||
|
SqlType<T> forDartType<T>() {
|
||||||
|
return types.singleWhere((t) => t is SqlType<T>);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import 'package:sally/src/runtime/components/component.dart';
|
||||||
|
import 'package:sally/src/runtime/expressions/expression.dart';
|
||||||
|
import 'package:sally/src/runtime/sql_types.dart';
|
||||||
|
|
||||||
|
Expression<BoolType> and(Expression<BoolType> a, Expression<BoolType> b) =>
|
||||||
|
AndExpression(a, b);
|
||||||
|
|
||||||
|
Expression<BoolType> not(Expression<BoolType> a) => NotExpression(a);
|
||||||
|
|
||||||
|
class AndExpression extends Expression<BoolType> with InfixOperator<BoolType> {
|
||||||
|
Expression<BoolType> left, right;
|
||||||
|
|
||||||
|
final String operator = "AND";
|
||||||
|
|
||||||
|
AndExpression(this.left, this.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrExpression extends Expression<BoolType> with InfixOperator<BoolType> {
|
||||||
|
Expression<BoolType> left, right;
|
||||||
|
|
||||||
|
final String operator = "AND";
|
||||||
|
|
||||||
|
OrExpression(this.left, this.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotExpression extends Expression<BoolType> {
|
||||||
|
Expression<BoolType> inner;
|
||||||
|
|
||||||
|
NotExpression(this.inner);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeInto(GenerationContext context) {
|
||||||
|
context.buffer.write('NOT ');
|
||||||
|
inner.writeInto(context);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:sally/src/runtime/components/component.dart';
|
||||||
|
import 'package:sally/src/runtime/sql_types.dart';
|
||||||
|
|
||||||
|
/// Any sql expression that evaluates to some generic value. This does not
|
||||||
|
/// include queries (which might evaluate to multiple values) but individual
|
||||||
|
/// columns, functions and operators.
|
||||||
|
abstract class Expression<T extends SqlType> implements Component {}
|
||||||
|
|
||||||
|
/// An expression that looks like "$a operator $b$, where $a and $b itself
|
||||||
|
/// are expressions and operator is any string.
|
||||||
|
abstract class InfixOperator<T extends SqlType> implements Expression<T> {
|
||||||
|
Expression get left;
|
||||||
|
Expression get right;
|
||||||
|
String get operator;
|
||||||
|
|
||||||
|
@visibleForOverriding
|
||||||
|
bool get placeBrackets => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeInto(GenerationContext context) {
|
||||||
|
_placeBracketIfNeeded(context, true);
|
||||||
|
|
||||||
|
left.writeInto(context);
|
||||||
|
|
||||||
|
_placeBracketIfNeeded(context, false);
|
||||||
|
context.writeWhitespace();
|
||||||
|
context.buffer.write(operator);
|
||||||
|
context.writeWhitespace();
|
||||||
|
_placeBracketIfNeeded(context, true);
|
||||||
|
|
||||||
|
right.writeInto(context);
|
||||||
|
|
||||||
|
_placeBracketIfNeeded(context, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _placeBracketIfNeeded(GenerationContext context, bool open) {
|
||||||
|
if (placeBrackets) context.buffer.write(open ? '(' : ')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ComparisonOperator { less, less_or_equal, equal, more_or_equal, more }
|
||||||
|
|
||||||
|
class Comparison extends InfixOperator<BoolType> {
|
||||||
|
static const Map<ComparisonOperator, String> operatorNames = {
|
||||||
|
ComparisonOperator.less: '<',
|
||||||
|
ComparisonOperator.less_or_equal: '<=',
|
||||||
|
ComparisonOperator.equal: '=',
|
||||||
|
ComparisonOperator.more_or_equal: '>=',
|
||||||
|
ComparisonOperator.more: '>'
|
||||||
|
};
|
||||||
|
|
||||||
|
final Expression left;
|
||||||
|
final Expression right;
|
||||||
|
final ComparisonOperator op;
|
||||||
|
|
||||||
|
final bool placeBrackets = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get operator => operatorNames[op];
|
||||||
|
|
||||||
|
Comparison(this.left, this.op, this.right);
|
||||||
|
|
||||||
|
Comparison.equal(this.left, this.right) : this.op = ComparisonOperator.equal;
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:sally/src/runtime/components/component.dart';
|
||||||
|
import 'package:sally/src/runtime/expressions/expression.dart';
|
||||||
|
import 'package:sally/src/runtime/sql_types.dart';
|
||||||
|
|
||||||
|
class LikeOperator extends Expression<BoolType> {
|
||||||
|
final Expression<StringType> target;
|
||||||
|
final Expression<StringType> regex;
|
||||||
|
|
||||||
|
LikeOperator(this.target, this.regex);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeInto(GenerationContext context) {
|
||||||
|
target.writeInto(context);
|
||||||
|
context.buffer.write(' LIKE ');
|
||||||
|
regex.writeInto(context);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export 'bools.dart' show and, not;
|
|
@ -0,0 +1,27 @@
|
||||||
|
import 'package:sally/src/runtime/components/component.dart';
|
||||||
|
import 'package:sally/src/runtime/expressions/expression.dart';
|
||||||
|
import 'package:sally/src/runtime/sql_types.dart';
|
||||||
|
|
||||||
|
class Variable<T, S extends SqlType<T>> extends Expression<S> {
|
||||||
|
final T value;
|
||||||
|
|
||||||
|
Variable(this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeInto(GenerationContext context) {
|
||||||
|
context.introduceVariable(value);
|
||||||
|
context.buffer.write("?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Constant<T, S extends SqlType<T>> extends Expression<S> {
|
||||||
|
final T value;
|
||||||
|
|
||||||
|
Constant(this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeInto(GenerationContext context) {
|
||||||
|
final type = context.database.typeSystem.forDartType<T>();
|
||||||
|
context.buffer.write(type.mapToSqlConstant(value));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/// A type that can be mapped from Dart to sql. The generic type parameter here
|
||||||
|
/// denotes the resolved dart type.
|
||||||
|
abstract class SqlType<T> {
|
||||||
|
const SqlType();
|
||||||
|
|
||||||
|
/// Maps the [content] to a value that we can send together with a prepared
|
||||||
|
/// statement to represent the given value.
|
||||||
|
dynamic mapToSqlVariable(T content);
|
||||||
|
|
||||||
|
/// Maps the given content to a sql literal that can be included in the query
|
||||||
|
/// string.
|
||||||
|
String mapToSqlConstant(T content);
|
||||||
|
|
||||||
|
/// Maps the response from sql back to a readable dart type.
|
||||||
|
T mapFromDatabaseResponse(dynamic response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A mapper for boolean values in sql. Booleans are represented as integers,
|
||||||
|
/// where 0 means false and any other value means true.
|
||||||
|
class BoolType extends SqlType<bool> {
|
||||||
|
const BoolType();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool mapFromDatabaseResponse(response) {
|
||||||
|
return !(response == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String mapToSqlConstant(bool content) {
|
||||||
|
return content ? "1" : "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
mapToSqlVariable(bool content) {
|
||||||
|
return content ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StringType extends SqlType<String> {
|
||||||
|
const StringType();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String mapFromDatabaseResponse(response) => response;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String mapToSqlConstant(String content) {
|
||||||
|
// TODO: implement mapToSqlConstant
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
mapToSqlVariable(String content) => content;
|
||||||
|
}
|
||||||
|
|
||||||
|
class IntType extends SqlType<int> {
|
||||||
|
const IntType();
|
||||||
|
|
||||||
|
@override
|
||||||
|
int mapFromDatabaseResponse(response) => response;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String mapToSqlConstant(int content) => content.toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
mapToSqlVariable(int content) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:sally/src/runtime/components/component.dart';
|
||||||
|
import 'package:sally/src/runtime/components/limit.dart';
|
||||||
|
import 'package:sally/src/runtime/components/where.dart';
|
||||||
|
import 'package:sally/src/runtime/executor/executor.dart';
|
||||||
|
import 'package:sally/src/runtime/expressions/bools.dart';
|
||||||
|
import 'package:sally/src/runtime/expressions/expression.dart';
|
||||||
|
import 'package:sally/src/runtime/sql_types.dart';
|
||||||
|
import 'package:sally/src/runtime/structure/table_info.dart';
|
||||||
|
|
||||||
|
/// Statement that operates on a table (select, update, insert, delete).
|
||||||
|
abstract class Query<Table> {
|
||||||
|
@protected
|
||||||
|
GeneratedDatabase database;
|
||||||
|
@protected
|
||||||
|
TableInfo<Table, dynamic> table;
|
||||||
|
|
||||||
|
Query(this.database, this.table);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
Where whereExpr;
|
||||||
|
@protected
|
||||||
|
Limit limitExpr;
|
||||||
|
|
||||||
|
void writeStartPart(GenerationContext ctx);
|
||||||
|
|
||||||
|
void where(Expression<BoolType> filter(Table tbl)) {
|
||||||
|
final predicate = filter(table.asDslTable);
|
||||||
|
|
||||||
|
if (whereExpr == null) {
|
||||||
|
whereExpr = Where(predicate);
|
||||||
|
} else {
|
||||||
|
whereExpr = Where(and(whereExpr.predicate, predicate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void limit(int limit, {int offset}) {
|
||||||
|
limitExpr = Limit(limit, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
GenerationContext constructQuery() {
|
||||||
|
final ctx = GenerationContext(database);
|
||||||
|
var needsWhitespace = false;
|
||||||
|
|
||||||
|
writeStartPart(ctx);
|
||||||
|
needsWhitespace = true;
|
||||||
|
|
||||||
|
if (whereExpr != null) {
|
||||||
|
if (needsWhitespace) ctx.writeWhitespace();
|
||||||
|
|
||||||
|
whereExpr.writeInto(ctx);
|
||||||
|
needsWhitespace = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limitExpr != null) {
|
||||||
|
if (needsWhitespace) ctx.writeWhitespace();
|
||||||
|
|
||||||
|
limitExpr.writeInto(ctx);
|
||||||
|
needsWhitespace = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.buffer.write(';');
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import 'package:sally/src/runtime/components/component.dart';
|
||||||
|
import 'package:sally/src/runtime/executor/executor.dart';
|
||||||
|
import 'package:sally/src/runtime/statements/query.dart';
|
||||||
|
import 'package:sally/src/runtime/structure/table_info.dart';
|
||||||
|
|
||||||
|
class SelectStatement<UserTable, DataType> extends Query<UserTable> {
|
||||||
|
@override
|
||||||
|
covariant TableInfo<UserTable, DataType> table;
|
||||||
|
|
||||||
|
SelectStatement(GeneratedDatabase database, this.table)
|
||||||
|
: super(database, table);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeStartPart(GenerationContext ctx) {
|
||||||
|
ctx.buffer.write('SELECT * FROM ${table.$tableName}');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads and returns all results from this select query.
|
||||||
|
Future<List<DataType>> get() async {
|
||||||
|
final ctx = constructQuery();
|
||||||
|
|
||||||
|
final results =
|
||||||
|
await ctx.database.executor.runSelect(ctx.sql, ctx.boundVariables);
|
||||||
|
return results.map(table.map).toList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
import 'package:sally/sally.dart';
|
||||||
|
import 'package:sally/src/runtime/components/component.dart';
|
||||||
|
import 'package:sally/src/runtime/expressions/expression.dart';
|
||||||
|
import 'package:sally/src/runtime/expressions/text.dart';
|
||||||
|
import 'package:sally/src/runtime/expressions/variables.dart';
|
||||||
|
import 'package:sally/src/runtime/sql_types.dart';
|
||||||
|
|
||||||
|
abstract class GeneratedColumn<T, S extends SqlType<T>> extends Column<T, S> {
|
||||||
|
String get $name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Expression<BoolType> equals(Expression<S> compare) =>
|
||||||
|
Comparison.equal(this, compare);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeInto(GenerationContext context) {
|
||||||
|
context.buffer.write($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Expression<BoolType> equalsVal(T compare) => equals(Variable<T, S>(compare));
|
||||||
|
}
|
||||||
|
|
||||||
|
class GeneratedTextColumn extends GeneratedColumn<String, StringType>
|
||||||
|
implements TextColumn {
|
||||||
|
final String $name;
|
||||||
|
|
||||||
|
GeneratedTextColumn(this.$name);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Expression<BoolType> like(String regex) =>
|
||||||
|
LikeOperator(this, Variable<String, StringType>(regex));
|
||||||
|
}
|
||||||
|
|
||||||
|
class GeneratedBoolColumn extends GeneratedColumn<bool, BoolType>
|
||||||
|
implements BoolColumn {
|
||||||
|
final String $name;
|
||||||
|
|
||||||
|
GeneratedBoolColumn(this.$name);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeInto(GenerationContext context) {
|
||||||
|
context.buffer.write('(');
|
||||||
|
context.buffer.write($name);
|
||||||
|
context.buffer.write(' = 1)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GeneratedIntColumn extends GeneratedColumn<int, IntType>
|
||||||
|
implements IntColumn {
|
||||||
|
final String $name;
|
||||||
|
|
||||||
|
GeneratedIntColumn(this.$name);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Expression<BoolType> isBiggerThan(int i) =>
|
||||||
|
Comparison(this, ComparisonOperator.more, Variable<int, IntType>(i));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Expression<BoolType> isSmallerThan(int i) =>
|
||||||
|
Comparison(this, ComparisonOperator.less, Variable<int, IntType>(i));
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import 'package:sally/sally.dart';
|
||||||
|
|
||||||
|
/// Base class for generated classes.
|
||||||
|
abstract class TableInfo<TableDsl, DataClass> {
|
||||||
|
TableDsl get asDslTable;
|
||||||
|
|
||||||
|
/// The table name in the sql table
|
||||||
|
String get $tableName;
|
||||||
|
List<Column> get $columns;
|
||||||
|
|
||||||
|
DataClass map(Map<String, dynamic> data);
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
import 'package:sally/sally.dart';
|
||||||
|
|
||||||
|
class Users extends Table {
|
||||||
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
TextColumn get name => text().withLength(min: 6, max: 32)();
|
||||||
|
BoolColumn get isAwesome => boolean()();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example tables and data classes, these would be generated by sally_generator
|
||||||
|
// in a real project
|
||||||
|
class UserDataObject {
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
UserDataObject(this.id, this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
class GeneratedUsersTable extends Users with TableInfo<Users, UserDataObject> {
|
||||||
|
final GeneratedDatabase db;
|
||||||
|
|
||||||
|
GeneratedUsersTable(this.db);
|
||||||
|
|
||||||
|
IntColumn id = GeneratedIntColumn("id");
|
||||||
|
TextColumn name = GeneratedTextColumn("name");
|
||||||
|
BoolColumn isAwesome = GeneratedBoolColumn("is_awesome");
|
||||||
|
@override
|
||||||
|
List<Column<dynamic, SqlType>> get $columns => [id, name, isAwesome];
|
||||||
|
@override
|
||||||
|
String get $tableName => "users";
|
||||||
|
@override
|
||||||
|
Users get asDslTable => this;
|
||||||
|
@override
|
||||||
|
UserDataObject map(Map<String, dynamic> data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestDatabase extends GeneratedDatabase {
|
||||||
|
TestDatabase(QueryExecutor executor)
|
||||||
|
: super(SqlTypeSystem.withDefaults(), executor);
|
||||||
|
|
||||||
|
GeneratedUsersTable get users => GeneratedUsersTable(this);
|
||||||
|
}
|
|
@ -1,95 +1,65 @@
|
||||||
import 'package:sally/sally.dart';
|
import 'package:sally/sally.dart';
|
||||||
import 'package:sally/src/queries/predicates/predicate.dart';
|
|
||||||
import 'package:sally/src/queries/table_structure.dart';
|
|
||||||
import 'package:test_api/test_api.dart';
|
import 'package:test_api/test_api.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
|
|
||||||
|
import 'generated_tables.dart';
|
||||||
|
|
||||||
class MockExecutor extends Mock implements QueryExecutor {}
|
class MockExecutor extends Mock implements QueryExecutor {}
|
||||||
|
|
||||||
class Users extends Table {
|
|
||||||
IntColumn get id => integer().autoIncrement()();
|
|
||||||
TextColumn get name => text().withLength(min: 6, max: 32)();
|
|
||||||
BoolColumn get isAwesome => boolean()();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Example tables and data classes, these would be generated by sally_generator
|
|
||||||
// in a real project
|
|
||||||
class UserDataObject {
|
|
||||||
final int id;
|
|
||||||
final String name;
|
|
||||||
UserDataObject(this.id, this.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
class GeneratedUsersTable extends Users
|
|
||||||
with TableStructure<Users, UserDataObject> {
|
|
||||||
@override
|
|
||||||
Users get asTable => this;
|
|
||||||
@override
|
|
||||||
UserDataObject parse(Map<String, dynamic> result) {
|
|
||||||
return UserDataObject(result["id"], result["name"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get sqlTableName => "users";
|
|
||||||
|
|
||||||
IntColumn id = StructuredIntColumn("id");
|
|
||||||
TextColumn name = StructuredTextColumn("name");
|
|
||||||
BoolColumn isAwesome = StructuredBoolColumn("is_awesome");
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
GeneratedUsersTable users;
|
TestDatabase db;
|
||||||
MockExecutor executor;
|
MockExecutor executor;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
users = GeneratedUsersTable();
|
|
||||||
executor = MockExecutor();
|
executor = MockExecutor();
|
||||||
users.executor = executor;
|
db = TestDatabase(executor);
|
||||||
|
|
||||||
when(executor.executeQuery(any, any)).thenAnswer((_) => Future.value([]));
|
when(executor.runSelect(any, any)).thenAnswer((_) => Future.value([]));
|
||||||
});
|
});
|
||||||
|
|
||||||
group("Generates SELECT statements", () {
|
group("Generates SELECT statements", () {
|
||||||
test("generates simple statements", () {
|
test("generates simple statements", () {
|
||||||
users.select().get();
|
db.select(db.users).get();
|
||||||
verify(executor.executeQuery("SELECT * FROM users ", any));
|
verify(executor.runSelect("SELECT * FROM users;", argThat(isEmpty)));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("generates limit statements", () {
|
test("generates limit statements", () {
|
||||||
(users.select()..limit(amount: 10)).get();
|
(db.select(db.users)..limit(10)).get();
|
||||||
verify(executor.executeQuery("SELECT * FROM users LIMIT 10 ", any));
|
verify(executor.runSelect(
|
||||||
|
"SELECT * FROM users LIMIT 10;", argThat(isEmpty)));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("generates like expressions", () {
|
test("generates like expressions", () {
|
||||||
(users.select()..where((u) => u.name.like("Dash%"))).get();
|
(db.select(db.users)..where((u) => u.name.like("Dash%"))).get();
|
||||||
verify(executor
|
verify(executor
|
||||||
.executeQuery("SELECT * FROM users WHERE name LIKE ? ", ["Dash%"]));
|
.runSelect("SELECT * FROM users WHERE name LIKE ?;", ["Dash%"]));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("generates complex predicates", () {
|
test("generates complex predicates", () {
|
||||||
(users.select()
|
(db.select(db.users)
|
||||||
..where(
|
..where((u) =>
|
||||||
(u) => not(u.name.equals("Dash")).and(u.id.isBiggerThan(12))))
|
and(not(u.name.equalsVal("Dash")), (u.id.isBiggerThan(12)))))
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
verify(executor.executeQuery(
|
verify(executor.runSelect(
|
||||||
"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", () {
|
test("generates expressions from boolean fields", () {
|
||||||
(users.select()..where((u) => u.isAwesome.isTrue())).get();
|
(db.select(db.users)..where((u) => u.isAwesome)).get();
|
||||||
|
|
||||||
verify(executor.executeQuery(
|
verify(executor.runSelect(
|
||||||
"SELECT * FROM users WHERE is_awesome = 1", any));
|
"SELECT * FROM users WHERE (is_awesome = 1);", argThat(isEmpty)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
group("Generates DELETE statements", () {
|
group("Generates DELETE statements", () {
|
||||||
test("without any constaints", () {
|
test("without any constraints", () {
|
||||||
users.delete().performDelete();
|
users.delete().performDelete();
|
||||||
|
|
||||||
verify(executor.executeDelete("DELETE FROM users ", argThat(isEmpty)));
|
verify(executor.executeDelete("DELETE FROM users ", argThat(isEmpty)));
|
||||||
});
|
});
|
||||||
});
|
});*/
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue