Correctly write variables in analyzed queries

This commit is contained in:
Simon Binder 2019-06-29 13:47:33 +02:00
parent 807d1ddff0
commit 646987ad69
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
6 changed files with 145 additions and 33 deletions

View File

@ -56,9 +56,16 @@ class TableWithoutPK extends Table {
tables: [TodosTable, Categories, Users, SharedTodos, TableWithoutPK],
queries: [
Sql(
'allTodosWithCategory',
'SELECT t.*, c.id as catId, c."desc" as catDesc '
'FROM todos t INNER JOIN categories c ON c.id = t.category'),
'allTodosWithCategory',
'SELECT t.*, c.id as catId, c."desc" as catDesc '
'FROM todos t INNER JOIN categories c ON c.id = t.category',
),
Sql(
'todosForUser',
'SELECT t.* FROM todos t '
'INNER JOIN shared_todos st ON st.todo = t.id '
'INNER JOIN users u ON u.id = st.user '
'WHERE u.id = :user'),
],
)
class TodoDb extends _$TodoDb {

View File

@ -1024,6 +1024,21 @@ class AllTodosWithCategoryResult {
});
}
class TodosForUserResult {
final int id;
final String title;
final String content;
final DateTime targetDate;
final int category;
TodosForUserResult({
this.id,
this.title,
this.content,
this.targetDate,
this.category,
});
}
abstract class _$TodoDb extends GeneratedDatabase {
_$TodoDb(QueryExecutor e) : super(const SqlTypeSystem.withDefaults(), e);
$TodosTableTable _todosTable;
@ -1051,16 +1066,44 @@ abstract class _$TodoDb extends GeneratedDatabase {
Future<List<AllTodosWithCategoryResult>> allTodosWithCategory() {
return customSelect(
'SELECT t.*, c.id as catId, c."desc" as catDesc FROM todos t INNER JOIN categories c ON c.id = t.category')
'SELECT t.*, c.id as catId, c."desc" as catDesc FROM todos t INNER JOIN categories c ON c.id = t.category',
variables: [])
.then((rows) => rows.map(_rowToAllTodosWithCategoryResult).toList());
}
Stream<List<AllTodosWithCategoryResult>> watchAllTodosWithCategory() {
return customSelectStream(
'SELECT t.*, c.id as catId, c."desc" as catDesc FROM todos t INNER JOIN categories c ON c.id = t.category')
'SELECT t.*, c.id as catId, c."desc" as catDesc FROM todos t INNER JOIN categories c ON c.id = t.category',
variables: [])
.map((rows) => rows.map(_rowToAllTodosWithCategoryResult).toList());
}
TodosForUserResult _rowToTodosForUserResult(QueryRow row) {
return TodosForUserResult(
id: row.readInt('id'),
title: row.readString('title'),
content: row.readString('content'),
targetDate: row.readDateTime('target_date'),
category: row.readInt('category'),
);
}
Future<List<TodosForUserResult>> todosForUser(int user) {
return customSelect(
'SELECT t.* FROM todos t INNER JOIN shared_todos st ON st.todo = t.id INNER JOIN users u ON u.id = st.user WHERE u.id = :user',
variables: [
Variable.withInt(user),
]).then((rows) => rows.map(_rowToTodosForUserResult).toList());
}
Stream<List<TodosForUserResult>> watchTodosForUser(int user) {
return customSelectStream(
'SELECT t.* FROM todos t INNER JOIN shared_todos st ON st.todo = t.id INNER JOIN users u ON u.id = st.user WHERE u.id = :user',
variables: [
Variable.withInt(user),
]).map((rows) => rows.map(_rowToTodosForUserResult).toList());
}
@override
List<TableInfo> get allTables =>
[todosTable, categories, users, sharedTodos, tableWithoutPK];

View File

@ -49,6 +49,17 @@ const Map<ColumnType, String> readFromMethods = {
ColumnType.real: 'readDouble',
};
/// Maps from a column type to code that can be used to create a variable of the
/// respective type.
const Map<ColumnType, String> createVariable = {
ColumnType.boolean: 'Variable.withBool',
ColumnType.text: 'Variable.withString',
ColumnType.integer: 'Variable.withInt',
ColumnType.datetime: 'Variable.withDateTime',
ColumnType.blob: 'Variable.withBlob',
ColumnType.real: 'Variable.withReal',
};
/// A column, as specified by a getter in a table.
class SpecifiedColumn {
/// The getter name of this column in the table class. It will also be used

View File

@ -8,8 +8,9 @@ final _leadingDigits = RegExp(r'^\d*');
abstract class SqlQuery {
final String name;
final String sql;
final List<FoundVariable> variables;
SqlQuery(this.name, this.sql);
SqlQuery(this.name, this.sql, this.variables);
}
class SqlSelectQuery extends SqlQuery {
@ -18,8 +19,9 @@ class SqlSelectQuery extends SqlQuery {
String get resultClassName => '${ReCase(name).pascalCase}Result';
SqlSelectQuery(String name, String sql, this.readsFrom, this.resultSet)
: super(name, sql);
SqlSelectQuery(String name, String sql, List<FoundVariable> variables,
this.readsFrom, this.resultSet)
: super(name, sql, variables);
}
class InferredResultSet {
@ -70,3 +72,14 @@ class ResultColumn {
ResultColumn(this.name, this.type, this.nullable);
}
class FoundVariable {
int index;
String name;
final ColumnType type;
FoundVariable(this.index, this.name, this.type);
String get dartParameterName =>
name?.replaceAll(_illegalChars, '') ?? 'var$index';
}

View File

@ -121,7 +121,33 @@ class SqlParser {
}
final resultSet = InferredResultSet(null, moorColumns);
foundQueries.add(
SqlSelectQuery(queryName, ctx.sql, moorTables.toList(), resultSet));
final foundVars = _extractVariables(ctx);
foundQueries.add(SqlSelectQuery(
queryName, ctx.sql, foundVars, moorTables.toList(), resultSet));
}
List<FoundVariable> _extractVariables(AnalysisContext ctx) {
// this contains variable references. For instance, SELECT :a = :a would
// contain two entries, both referring to the same variable. To do that,
// we use the fact that each variable has a unique index.
final usedVars = ctx.root.allDescendants.whereType<Variable>().toList()
..sort((a, b) => a.resolvedIndex.compareTo(b.resolvedIndex));
final foundVariables = <FoundVariable>[];
var currentIndex = 0;
for (var used in usedVars) {
if (used.resolvedIndex == currentIndex) {
continue; // already handled
}
currentIndex++;
final name = (used is ColonNamedVariable) ? used.name : null;
final type = _resolvedToMoor(ctx.typeOf(used).type);
foundVariables.add(FoundVariable(currentIndex, name, type));
}
return foundVariables;
}
}

View File

@ -11,24 +11,6 @@ class QueryWriter {
QueryWriter(this.query);
/* The generated code will look like:
Future<List<AllTodosWithCategoryResult>> allTodosWithCategory() {
return customSelect('', variables: [])
.then((rows) => rows.map(_rowToAllT).toList());
}
Stream<List<AllTodosWithCategoryResult>> watchAllTodosWithCategory() {
return customSelectStream('', variables: [])
.map((rows) => rows.map(_rowToAllT).toList());
}
AllTodosWithCategoryResult _rowToAllT(QueryRow row) {
return AllTodosWithCategoryResult(
id: row.readInt('id'),
);
}
*/
void writeInto(StringBuffer buffer) {
if (query is SqlSelectQuery) {
_writeSelect(buffer);
@ -63,9 +45,14 @@ class QueryWriter {
}
void _writeOneTimeReader(StringBuffer buffer) {
buffer.write('Future<List<${_select.resultClassName}>> ${query.name}(');
_writeParameters(buffer);
buffer
..write('Future<List<${_select.resultClassName}>> ${query.name}() {\n')
..write('return customSelect(${asDartLiteral(query.sql)})')
..write(') {\n')
..write('return customSelect(${asDartLiteral(query.sql)},');
_writeVariables(buffer);
buffer
..write(')')
..write(
'.then((rows) => rows.map(${_nameOfMappingMethod()}).toList());\n')
..write('\n}\n');
@ -73,11 +60,36 @@ class QueryWriter {
void _writeStreamReader(StringBuffer buffer) {
final upperQueryName = ReCase(query.name).pascalCase;
buffer.write(
'Stream<List<${_select.resultClassName}>> watch$upperQueryName(');
_writeParameters(buffer);
buffer
..write('Stream<List<${_select.resultClassName}>> watch$upperQueryName')
..write('() {\n')
..write('return customSelectStream(${asDartLiteral(query.sql)})')
..write(') {\n')
..write('return customSelectStream(${asDartLiteral(query.sql)},');
_writeVariables(buffer);
buffer
..write(')')
..write('.map((rows) => rows.map(${_nameOfMappingMethod()}).toList());\n')
..write('\n}\n');
}
void _writeParameters(StringBuffer buffer) {
final paramList = query.variables
.map((v) => '${dartTypeNames[v.type]} ${v.dartParameterName}')
.join(', ');
buffer.write(paramList);
}
void _writeVariables(StringBuffer buffer) {
buffer..write('variables: [');
for (var variable in query.variables) {
buffer
..write(createVariable[variable.type])
..write('(${variable.dartParameterName}),');
}
buffer..write(']');
}
}