Fix some bugs in the sql parser, another integration test

This commit is contained in:
Simon Binder
2019-08-17 22:21:34 +02:00
parent 4ca7e18027
commit 3dcf59af63
10 changed files with 96 additions and 7 deletions
@@ -69,6 +69,10 @@ class PreferenceConverter extends TypeConverter<Preferences, String> {
'SELECT * FROM users u ORDER BY (SELECT COUNT(*) FROM friendships WHERE first_user = u.id OR second_user = u.id) DESC LIMIT :amount', 'SELECT * FROM users u ORDER BY (SELECT COUNT(*) FROM friendships WHERE first_user = u.id OR second_user = u.id) DESC LIMIT :amount',
'amountOfGoodFriends': 'amountOfGoodFriends':
'SELECT COUNT(*) FROM friendships f WHERE f.really_good_friends AND (f.first_user = :user OR f.second_user = :user)', 'SELECT COUNT(*) FROM friendships f WHERE f.really_good_friends AND (f.first_user = :user OR f.second_user = :user)',
'friendsOf': '''SELECT u.* FROM friendships f
INNER JOIN users u ON u.id IN (f.first_user, f.second_user) AND
u.id != :user
WHERE (f.first_user = :user OR f.second_user = :user)''',
'userCount': 'SELECT COUNT(id) FROM users', 'userCount': 'SELECT COUNT(id) FROM users',
'settingsFor': 'SELECT preferences FROM users WHERE id = :user', 'settingsFor': 'SELECT preferences FROM users WHERE id = :user',
}, },
@@ -147,6 +147,20 @@ class UsersCompanion extends UpdateCompanion<User> {
this.profilePicture = const Value.absent(), this.profilePicture = const Value.absent(),
this.preferences = const Value.absent(), this.preferences = const Value.absent(),
}); });
UsersCompanion copyWith(
{Value<int> id,
Value<String> name,
Value<DateTime> birthDate,
Value<Uint8List> profilePicture,
Value<Preferences> preferences}) {
return UsersCompanion(
id: id ?? this.id,
name: name ?? this.name,
birthDate: birthDate ?? this.birthDate,
profilePicture: profilePicture ?? this.profilePicture,
preferences: preferences ?? this.preferences,
);
}
} }
class $UsersTable extends Users with TableInfo<$UsersTable, User> { class $UsersTable extends Users with TableInfo<$UsersTable, User> {
@@ -388,6 +402,16 @@ class FriendshipsCompanion extends UpdateCompanion<Friendship> {
this.secondUser = const Value.absent(), this.secondUser = const Value.absent(),
this.reallyGoodFriends = const Value.absent(), this.reallyGoodFriends = const Value.absent(),
}); });
FriendshipsCompanion copyWith(
{Value<int> firstUser,
Value<int> secondUser,
Value<bool> reallyGoodFriends}) {
return FriendshipsCompanion(
firstUser: firstUser ?? this.firstUser,
secondUser: secondUser ?? this.secondUser,
reallyGoodFriends: reallyGoodFriends ?? this.reallyGoodFriends,
);
}
} }
class $FriendshipsTable extends Friendships class $FriendshipsTable extends Friendships
@@ -585,6 +609,29 @@ abstract class _$Database extends GeneratedDatabase {
}).map((rows) => rows.map(_rowToAmountOfGoodFriendsResult).toList()); }).map((rows) => rows.map(_rowToAmountOfGoodFriendsResult).toList());
} }
Future<List<User>> friendsOf(
int user,
{@Deprecated('No longer needed with Moor 1.6 - see the changelog for details')
QueryEngine operateOn}) {
return (operateOn ?? this).customSelect(
'SELECT u.* FROM friendships f\n INNER JOIN users u ON u.id IN (f.first_user, f.second_user) AND\n u.id != :user\n WHERE (f.first_user = :user OR f.second_user = :user)',
variables: [
Variable.withInt(user),
]).then((rows) => rows.map(_rowToUser).toList());
}
Stream<List<User>> watchFriendsOf(int user) {
return customSelectStream(
'SELECT u.* FROM friendships f\n INNER JOIN users u ON u.id IN (f.first_user, f.second_user) AND\n u.id != :user\n WHERE (f.first_user = :user OR f.second_user = :user)',
variables: [
Variable.withInt(user),
],
readsFrom: {
friendships,
users
}).map((rows) => rows.map(_rowToUser).toList());
}
UserCountResult _rowToUserCountResult(QueryRow row) { UserCountResult _rowToUserCountResult(QueryRow row) {
return UserCountResult( return UserCountResult(
cOUNTid: row.readInt('COUNT(id)'), cOUNTid: row.readInt('COUNT(id)'),
@@ -0,0 +1,26 @@
import 'package:test/test.dart';
import 'package:tests/database/database.dart';
import 'package:tests/suite/suite.dart';
void crudTests(TestExecutor executor) {
test('inserting updates a select stream', () async {
final db = Database(executor.createExecutor());
final friends = db.watchFriendsOf(1);
final a = await db.getUserById(1);
final b = await db.getUserById(2);
final expectation = expectLater(
friends,
emitsInOrder(
[
isEmpty, // initial state without friendships
[b] // after we called makeFriends(a,b)
],
),
);
await db.makeFriends(a, b);
await expectation;
});
}
@@ -1,5 +1,6 @@
import 'package:moor/moor.dart'; import 'package:moor/moor.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:tests/suite/crud_tests.dart';
import 'package:tests/suite/transactions.dart'; import 'package:tests/suite/transactions.dart';
import 'custom_objects.dart'; import 'custom_objects.dart';
@@ -17,6 +18,7 @@ void runAllTests(TestExecutor executor) {
await executor.deleteData(); await executor.deleteData();
}); });
crudTests(executor);
migrationTests(executor); migrationTests(executor);
customObjectTests(executor); customObjectTests(executor);
transactionTests(executor); transactionTests(executor);
@@ -23,3 +23,5 @@ dependency_overrides:
path: ../../../moor path: ../../../moor
moor_generator: moor_generator:
path: ../../../moor_generator path: ../../../moor_generator
sqlparser:
path: ../../../sqlparser
@@ -1,4 +1,5 @@
import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/constant/value.dart';
import 'package:build/build.dart';
import 'package:moor_generator/src/state/errors.dart'; import 'package:moor_generator/src/state/errors.dart';
import 'package:moor_generator/src/model/specified_table.dart'; import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/model/sql_query.dart'; import 'package:moor_generator/src/model/sql_query.dart';
@@ -49,8 +50,8 @@ class SqlParser {
try { try {
foundQueries.add(QueryHandler(name, context, _mapper).handle()); foundQueries.add(QueryHandler(name, context, _mapper).handle());
} catch (e) { } catch (e, s) {
print('Error while generating APIs for ${context.sql}: $e'); log.warning('Error while generating APIs for ${context.sql}', e, s);
} }
}); });
} }
+1 -1
View File
@@ -1,7 +1,7 @@
# We use netlify to deploy the documentation. # We use netlify to deploy the documentation.
[build] [build]
base = "docs/" base = "docs"
publish = "docs/public" publish = "docs/public"
command = 'git submodule update --init --recursive && HUGO_ENV="production" hugo' command = 'git submodule update --init --recursive && HUGO_ENV="production" hugo'
@@ -136,7 +136,9 @@ class TypeResolver {
if (sqlParameters is ExprFunctionParameters) { if (sqlParameters is ExprFunctionParameters) {
return sqlParameters.parameters; return sqlParameters.parameters;
} else if (sqlParameters is StarFunctionParameter) { } else if (sqlParameters is StarFunctionParameter) {
return call.scope.availableColumns; // if * is used as a parameter, it refers to all columns in all tables
// that are available in the current scope.
return call.scope.availableColumns.whereType<TableColumn>().toList();
} }
throw ArgumentError('Unknown parameters: $sqlParameters'); throw ArgumentError('Unknown parameters: $sqlParameters');
} }
@@ -95,6 +95,11 @@ class Scanner {
_addToken(TokenType.more); _addToken(TokenType.more);
} }
break; break;
case '!':
if (_match('=')) {
_addToken(TokenType.exclamationEqual);
}
break;
case '=': case '=':
_addToken(_match('=') ? TokenType.doubleEqual : TokenType.equal); _addToken(_match('=') ? TokenType.doubleEqual : TokenType.equal);
break; break;
@@ -138,8 +143,7 @@ class Scanner {
} else if (canStartColumnName(char)) { } else if (canStartColumnName(char)) {
_identifier(); _identifier();
} else { } else {
errors.add(TokenizerError( errors.add(TokenizerError('Unexpected character.', _currentLocation));
'Unexpected character.', SourceLocation(_currentOffset)));
} }
break; break;
} }
@@ -35,6 +35,7 @@ Map<String, TokenType> testCases = {
'<': TokenType.less, '<': TokenType.less,
'>=': TokenType.moreEqual, '>=': TokenType.moreEqual,
'>': TokenType.more, '>': TokenType.more,
'!=': TokenType.exclamationEqual,
"'hello there'": TokenType.stringLiteral, "'hello there'": TokenType.stringLiteral,
'1.123': TokenType.numberLiteral, '1.123': TokenType.numberLiteral,
'1.32e5': TokenType.numberLiteral, '1.32e5': TokenType.numberLiteral,