mirror of https://github.com/AMT-Cheif/drift.git
Correctly write variables in analyzed queries
This commit is contained in:
parent
807d1ddff0
commit
646987ad69
|
@ -56,9 +56,16 @@ class TableWithoutPK extends Table {
|
||||||
tables: [TodosTable, Categories, Users, SharedTodos, TableWithoutPK],
|
tables: [TodosTable, Categories, Users, SharedTodos, TableWithoutPK],
|
||||||
queries: [
|
queries: [
|
||||||
Sql(
|
Sql(
|
||||||
'allTodosWithCategory',
|
'allTodosWithCategory',
|
||||||
'SELECT t.*, c.id as catId, c."desc" as catDesc '
|
'SELECT t.*, c.id as catId, c."desc" as catDesc '
|
||||||
'FROM todos t INNER JOIN categories c ON c.id = t.category'),
|
'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 {
|
class TodoDb extends _$TodoDb {
|
||||||
|
|
|
@ -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 {
|
abstract class _$TodoDb extends GeneratedDatabase {
|
||||||
_$TodoDb(QueryExecutor e) : super(const SqlTypeSystem.withDefaults(), e);
|
_$TodoDb(QueryExecutor e) : super(const SqlTypeSystem.withDefaults(), e);
|
||||||
$TodosTableTable _todosTable;
|
$TodosTableTable _todosTable;
|
||||||
|
@ -1051,16 +1066,44 @@ abstract class _$TodoDb extends GeneratedDatabase {
|
||||||
|
|
||||||
Future<List<AllTodosWithCategoryResult>> allTodosWithCategory() {
|
Future<List<AllTodosWithCategoryResult>> allTodosWithCategory() {
|
||||||
return customSelect(
|
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());
|
.then((rows) => rows.map(_rowToAllTodosWithCategoryResult).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<AllTodosWithCategoryResult>> watchAllTodosWithCategory() {
|
Stream<List<AllTodosWithCategoryResult>> watchAllTodosWithCategory() {
|
||||||
return customSelectStream(
|
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());
|
.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
|
@override
|
||||||
List<TableInfo> get allTables =>
|
List<TableInfo> get allTables =>
|
||||||
[todosTable, categories, users, sharedTodos, tableWithoutPK];
|
[todosTable, categories, users, sharedTodos, tableWithoutPK];
|
||||||
|
|
|
@ -49,6 +49,17 @@ const Map<ColumnType, String> readFromMethods = {
|
||||||
ColumnType.real: 'readDouble',
|
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.
|
/// A column, as specified by a getter in a table.
|
||||||
class SpecifiedColumn {
|
class SpecifiedColumn {
|
||||||
/// The getter name of this column in the table class. It will also be used
|
/// The getter name of this column in the table class. It will also be used
|
||||||
|
|
|
@ -8,8 +8,9 @@ final _leadingDigits = RegExp(r'^\d*');
|
||||||
abstract class SqlQuery {
|
abstract class SqlQuery {
|
||||||
final String name;
|
final String name;
|
||||||
final String sql;
|
final String sql;
|
||||||
|
final List<FoundVariable> variables;
|
||||||
|
|
||||||
SqlQuery(this.name, this.sql);
|
SqlQuery(this.name, this.sql, this.variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SqlSelectQuery extends SqlQuery {
|
class SqlSelectQuery extends SqlQuery {
|
||||||
|
@ -18,8 +19,9 @@ class SqlSelectQuery extends SqlQuery {
|
||||||
|
|
||||||
String get resultClassName => '${ReCase(name).pascalCase}Result';
|
String get resultClassName => '${ReCase(name).pascalCase}Result';
|
||||||
|
|
||||||
SqlSelectQuery(String name, String sql, this.readsFrom, this.resultSet)
|
SqlSelectQuery(String name, String sql, List<FoundVariable> variables,
|
||||||
: super(name, sql);
|
this.readsFrom, this.resultSet)
|
||||||
|
: super(name, sql, variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
class InferredResultSet {
|
class InferredResultSet {
|
||||||
|
@ -70,3 +72,14 @@ class ResultColumn {
|
||||||
|
|
||||||
ResultColumn(this.name, this.type, this.nullable);
|
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';
|
||||||
|
}
|
||||||
|
|
|
@ -121,7 +121,33 @@ class SqlParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
final resultSet = InferredResultSet(null, moorColumns);
|
final resultSet = InferredResultSet(null, moorColumns);
|
||||||
foundQueries.add(
|
final foundVars = _extractVariables(ctx);
|
||||||
SqlSelectQuery(queryName, ctx.sql, moorTables.toList(), resultSet));
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,24 +11,6 @@ class QueryWriter {
|
||||||
|
|
||||||
QueryWriter(this.query);
|
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) {
|
void writeInto(StringBuffer buffer) {
|
||||||
if (query is SqlSelectQuery) {
|
if (query is SqlSelectQuery) {
|
||||||
_writeSelect(buffer);
|
_writeSelect(buffer);
|
||||||
|
@ -63,9 +45,14 @@ class QueryWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeOneTimeReader(StringBuffer buffer) {
|
void _writeOneTimeReader(StringBuffer buffer) {
|
||||||
|
buffer.write('Future<List<${_select.resultClassName}>> ${query.name}(');
|
||||||
|
_writeParameters(buffer);
|
||||||
buffer
|
buffer
|
||||||
..write('Future<List<${_select.resultClassName}>> ${query.name}() {\n')
|
..write(') {\n')
|
||||||
..write('return customSelect(${asDartLiteral(query.sql)})')
|
..write('return customSelect(${asDartLiteral(query.sql)},');
|
||||||
|
_writeVariables(buffer);
|
||||||
|
buffer
|
||||||
|
..write(')')
|
||||||
..write(
|
..write(
|
||||||
'.then((rows) => rows.map(${_nameOfMappingMethod()}).toList());\n')
|
'.then((rows) => rows.map(${_nameOfMappingMethod()}).toList());\n')
|
||||||
..write('\n}\n');
|
..write('\n}\n');
|
||||||
|
@ -73,11 +60,36 @@ class QueryWriter {
|
||||||
|
|
||||||
void _writeStreamReader(StringBuffer buffer) {
|
void _writeStreamReader(StringBuffer buffer) {
|
||||||
final upperQueryName = ReCase(query.name).pascalCase;
|
final upperQueryName = ReCase(query.name).pascalCase;
|
||||||
|
buffer.write(
|
||||||
|
'Stream<List<${_select.resultClassName}>> watch$upperQueryName(');
|
||||||
|
_writeParameters(buffer);
|
||||||
buffer
|
buffer
|
||||||
..write('Stream<List<${_select.resultClassName}>> watch$upperQueryName')
|
..write(') {\n')
|
||||||
..write('() {\n')
|
..write('return customSelectStream(${asDartLiteral(query.sql)},');
|
||||||
..write('return customSelectStream(${asDartLiteral(query.sql)})')
|
_writeVariables(buffer);
|
||||||
|
buffer
|
||||||
|
..write(')')
|
||||||
..write('.map((rows) => rows.map(${_nameOfMappingMethod()}).toList());\n')
|
..write('.map((rows) => rows.map(${_nameOfMappingMethod()}).toList());\n')
|
||||||
..write('\n}\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(']');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue