Don't write variables in `CREATE VIEW`

This commit is contained in:
Simon Binder 2022-03-05 21:56:51 +01:00
parent 3c50437821
commit 4ddbed5cca
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
8 changed files with 35 additions and 10 deletions

View File

@ -3,6 +3,7 @@
- Add `DataClassName.extending` to control the superclass of generated row
classes.
- Add `setup` parameter to the constructors of `WebDatabase` too.
- Don't write variables for expressions in `CREATE VIEW` statements.
## 1.4.0

View File

@ -60,6 +60,12 @@ class Variable<T> extends Expression<T> {
@override
void writeInto(GenerationContext context) {
if (!context.supportsVariables) {
// Write as constant instead.
Constant<T>(value).writeInto(context);
return;
}
var explicitStart = context.explicitVariableIndex;
var mark = '?';

View File

@ -30,6 +30,12 @@ class GenerationContext {
/// query.
final DatabaseConnectionUser? executor;
/// Whether variables are supported and can be written as `?` to be bound
/// later.
///
/// This is almost always the case, but not in a `CREATE VIEW` statement.
final bool supportsVariables;
final List<dynamic> _boundVariables = [];
/// The values of [introducedVariables] that will be sent to the underlying
@ -51,7 +57,7 @@ class GenerationContext {
/// Constructs a [GenerationContext] by copying the relevant fields from the
/// database.
GenerationContext.fromDb(this.executor)
GenerationContext.fromDb(this.executor, {this.supportsVariables = true})
: typeSystem = executor?.typeSystem ?? SqlTypeSystem.defaultInstance,
// ignore: invalid_null_aware_operator, (doesn't seem to actually work)
dialect = executor?.executor?.dialect ?? SqlDialect.sqlite;
@ -59,7 +65,7 @@ class GenerationContext {
/// Constructs a custom [GenerationContext] by setting the fields manually.
/// See [GenerationContext.fromDb] for a more convenient factory.
GenerationContext(this.typeSystem, this.executor,
{this.dialect = SqlDialect.sqlite});
{this.dialect = SqlDialect.sqlite, this.supportsVariables = true});
/// Introduces a variable that will be sent to the database engine. Whenever
/// this method is called, a question mark should be added to the [buffer] so

View File

@ -324,7 +324,7 @@ class Migrator {
if (stmt != null) {
await _issueCustomQuery(stmt, const []);
} else if (view.query != null) {
final context = GenerationContext.fromDb(_db);
final context = GenerationContext.fromDb(_db, supportsVariables: false);
context.generatingForView = view.entityName;
context.buffer.write('CREATE VIEW IF NOT EXISTS ${view.entityName} AS ');
view.query!.writeInto(context);

View File

@ -121,10 +121,12 @@ abstract class CategoryTodoCountView extends View {
TodosTable get todos;
Categories get categories;
Expression<String> get description =>
categories.description + const Variable('!');
Expression<int> get itemCount => todos.id.count();
@override
Query as() => select([categories.description, itemCount])
Query as() => select([description, itemCount])
.from(categories)
.join([innerJoin(todos, todos.category.equalsExp(categories.id))]);
}

View File

@ -1362,7 +1362,7 @@ class CategoryTodoCountViewData extends DataClass {
final effectivePrefix = prefix ?? '';
return CategoryTodoCountViewData(
description: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}categories.desc'])!,
.mapFromDatabaseResponse(data['${effectivePrefix}description'])!,
itemCount: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}item_count'])!,
);
@ -1423,7 +1423,7 @@ class $CategoryTodoCountViewView
$TodosTableTable get todos => attachedDatabase.todosTable;
$CategoriesTable get categories => attachedDatabase.categories;
@override
List<GeneratedColumn> get $columns => [categories.description, itemCount];
List<GeneratedColumn> get $columns => [description, itemCount];
@override
String get aliasedName => _alias ?? entityName;
@override
@ -1440,8 +1440,10 @@ class $CategoryTodoCountViewView
}
late final GeneratedColumn<String?> description = GeneratedColumn<String?>(
'desc', aliasedName, false,
type: const StringType(), $customConstraints: 'NOT NULL UNIQUE');
'description', aliasedName, false,
type: const StringType(),
generatedAs:
GeneratedAs(categories.description + const Variable('!'), false));
late final GeneratedColumn<int?> itemCount = GeneratedColumn<int?>(
'item_count', aliasedName, false,
type: const IntType(), generatedAs: GeneratedAs(todos.id.count(), false));

View File

@ -54,7 +54,15 @@ void main() {
variable.writeInto(ctx);
expect('NULL', ctx.sql);
expect(ctx.sql, 'NULL');
expect(ctx.boundVariables, isEmpty);
});
test('writes constants when variables are not supported', () {
const variable = Variable<String?>("hello world'");
final ctx = GenerationContext.fromDb(TodoDb(), supportsVariables: false);
variable.writeInto(ctx);
expect(ctx.sql, "'hello world'''");
});
}

View File

@ -67,7 +67,7 @@ void main() {
verify(mockExecutor.runCustom(
'CREATE VIEW IF NOT EXISTS category_todo_count_view AS SELECT '
'categories."desc" AS "categories.desc", '
'categories."desc" || \'!\' AS "description", '
'COUNT(todos.id) AS "item_count" '
'FROM categories '
'INNER JOIN todos '