Provide proper generation context during migrations

This commit is contained in:
Simon Binder 2019-05-09 10:54:32 +02:00
parent 12d510e78f
commit 50690290ec
No known key found for this signature in database
GPG Key ID: B807FDF954BA00CF
17 changed files with 122 additions and 68 deletions

View File

@ -18,7 +18,8 @@ class GenerationContext {
/// queries. /// queries.
bool hasMultipleTables = false; bool hasMultipleTables = false;
final QueryEngine database; final SqlTypeSystem typeSystem;
final QueryExecutor executor;
final List<dynamic> _boundVariables = []; final List<dynamic> _boundVariables = [];
List<dynamic> get boundVariables => _boundVariables; List<dynamic> get boundVariables => _boundVariables;
@ -29,7 +30,11 @@ class GenerationContext {
/// Gets the generated sql statement /// Gets the generated sql statement
String get sql => buffer.toString(); String get sql => buffer.toString();
GenerationContext(this.database); GenerationContext.fromDb(QueryEngine database)
: typeSystem = database.typeSystem,
executor = database.executor;
GenerationContext(this.typeSystem, this.executor);
/// Introduces a variable that will be sent to the database engine. Whenever /// 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 /// this method is called, a question mark should be added to the [buffer] so

View File

@ -124,7 +124,7 @@ mixin QueryEngine on DatabaseConnectionUser {
/// specified there will then issue another query. /// specified there will then issue another query.
Future<int> customUpdate(String query, Future<int> customUpdate(String query,
{List<Variable> variables = const [], Set<TableInfo> updates}) async { {List<Variable> variables = const [], Set<TableInfo> updates}) async {
final ctx = GenerationContext(this); final ctx = GenerationContext.fromDb(this);
final mappedArgs = variables.map((v) => v.mapToSimpleValue(ctx)).toList(); final mappedArgs = variables.map((v) => v.mapToSimpleValue(ctx)).toList();
final affectedRows = final affectedRows =

View File

@ -40,7 +40,7 @@ class Variable<T, S extends SqlType<T>> extends Expression<T, S> {
/// database engine. For instance, a [DateTime] will me mapped to its unix /// database engine. For instance, a [DateTime] will me mapped to its unix
/// timestamp. /// timestamp.
dynamic mapToSimpleValue(GenerationContext context) { dynamic mapToSimpleValue(GenerationContext context) {
final type = context.database.typeSystem.forDartType<T>(); final type = context.typeSystem.forDartType<T>();
return type.mapToSqlVariable(value); return type.mapToSqlVariable(value);
} }
@ -69,7 +69,7 @@ class Constant<T, S extends SqlType<T>> extends Expression<T, S> {
@override @override
void writeInto(GenerationContext context) { void writeInto(GenerationContext context) {
final type = context.database.typeSystem.forDartType<T>(); final type = context.typeSystem.forDartType<T>();
context.buffer.write(type.mapToSqlConstant(value)); context.buffer.write(type.mapToSqlConstant(value));
} }
} }

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:moor/moor.dart'; import 'package:moor/moor.dart';
import 'package:moor/src/runtime/components/component.dart';
import 'package:moor/src/runtime/structure/columns.dart'; import 'package:moor/src/runtime/structure/columns.dart';
import 'package:moor/src/runtime/structure/table_info.dart'; import 'package:moor/src/runtime/structure/table_info.dart';
@ -51,10 +52,15 @@ class Migrator {
return Future.wait(_db.allTables.map(createTable)); return Future.wait(_db.allTables.map(createTable));
} }
GenerationContext _createContext() {
return GenerationContext(
_db.typeSystem, _SimpleSqlAsQueryExecutor(_executor));
}
/// Creates the given table if it doesn't exist /// Creates the given table if it doesn't exist
Future<void> createTable(TableInfo table) async { Future<void> createTable(TableInfo table) async {
final sql = StringBuffer() final context = _createContext();
..write('CREATE TABLE IF NOT EXISTS ${table.$tableName} ('); context.buffer.write('CREATE TABLE IF NOT EXISTS ${table.$tableName} (');
var hasAutoIncrement = false; var hasAutoIncrement = false;
for (var i = 0; i < table.$columns.length; i++) { for (var i = 0; i < table.$columns.length; i++) {
@ -64,34 +70,34 @@ class Migrator {
hasAutoIncrement = true; hasAutoIncrement = true;
} }
column.writeColumnDefinition(sql); column.writeColumnDefinition(context);
if (i < table.$columns.length - 1) sql.write(', '); if (i < table.$columns.length - 1) context.buffer.write(', ');
} }
final hasPrimaryKey = table.$primaryKey?.isNotEmpty ?? false; final hasPrimaryKey = table.$primaryKey?.isNotEmpty ?? false;
if (hasPrimaryKey && !hasAutoIncrement) { if (hasPrimaryKey && !hasAutoIncrement) {
sql.write(', PRIMARY KEY ('); context.buffer.write(', PRIMARY KEY (');
final pkList = table.$primaryKey.toList(growable: false); final pkList = table.$primaryKey.toList(growable: false);
for (var i = 0; i < pkList.length; i++) { for (var i = 0; i < pkList.length; i++) {
final column = pkList[i]; final column = pkList[i];
sql.write(column.$name); context.buffer.write(column.$name);
if (i != pkList.length - 1) sql.write(', '); if (i != pkList.length - 1) context.buffer.write(', ');
} }
sql.write(')'); context.buffer.write(')');
} }
final constraints = table.asDslTable.customConstraints ?? []; final constraints = table.asDslTable.customConstraints ?? [];
for (var i = 0; i < constraints.length; i++) { for (var i = 0; i < constraints.length; i++) {
sql..write(', ')..write(constraints[i]); context.buffer..write(', ')..write(constraints[i]);
} }
sql.write(');'); context.buffer.write(');');
return issueCustomQuery(sql.toString()); return issueCustomQuery(context.sql);
} }
/// Deletes the table with the given name. Note that this function does not /// Deletes the table with the given name. Note that this function does not
@ -102,14 +108,13 @@ class Migrator {
/// Adds the given column to the specified table. /// Adds the given column to the specified table.
Future<void> addColumn(TableInfo table, GeneratedColumn column) async { Future<void> addColumn(TableInfo table, GeneratedColumn column) async {
final sql = StringBuffer(); final context = _createContext();
// ignore: cascade_invocations context.buffer.write('ALTER TABLE ${table.$tableName} ADD COLUMN ');
sql.write('ALTER TABLE ${table.$tableName} ADD COLUMN '); column.writeColumnDefinition(context);
column.writeColumnDefinition(sql); context.buffer.write(';');
sql.write(';');
return issueCustomQuery(sql.toString()); return issueCustomQuery(context.sql);
} }
/// Executes the custom query. /// Executes the custom query.
@ -117,3 +122,49 @@ class Migrator {
return _executor(sql); return _executor(sql);
} }
} }
class _SimpleSqlAsQueryExecutor extends QueryExecutor {
final SqlExecutor executor;
_SimpleSqlAsQueryExecutor(this.executor);
@override
TransactionExecutor beginTransaction() {
throw UnsupportedError('Not supported for migrations');
}
@override
Future<bool> ensureOpen() {
return Future.value(true);
}
@override
Future<void> runBatched(List<BatchedStatement> statements) {
throw UnsupportedError('Not supported for migrations');
}
@override
Future<void> runCustom(String statement) {
return executor(statement);
}
@override
Future<int> runDelete(String statement, List args) {
throw UnsupportedError('Not supported for migrations');
}
@override
Future<int> runInsert(String statement, List args) {
throw UnsupportedError('Not supported for migrations');
}
@override
Future<List<Map<String, dynamic>>> runSelect(String statement, List args) {
throw UnsupportedError('Not supported for migrations');
}
@override
Future<int> runUpdate(String statement, List args) {
throw UnsupportedError('Not supported for migrations');
}
}

View File

@ -32,9 +32,8 @@ class DeleteStatement<T extends Table, D> extends Query<T, D>
Future<int> go() async { Future<int> go() async {
final ctx = constructQuery(); final ctx = constructQuery();
return ctx.database.executor.doWhenOpened((e) async { return ctx.executor.doWhenOpened((e) async {
final rows = final rows = await ctx.executor.runDelete(ctx.sql, ctx.boundVariables);
await ctx.database.executor.runDelete(ctx.sql, ctx.boundVariables);
if (rows > 0) { if (rows > 0) {
database.markTablesUpdated({table}); database.markTablesUpdated({table});

View File

@ -86,7 +86,7 @@ class InsertStatement<DataClass> {
final map = table.entityToSql(entry) final map = table.entityToSql(entry)
..removeWhere((_, value) => value == null); ..removeWhere((_, value) => value == null);
final ctx = GenerationContext(database); final ctx = GenerationContext.fromDb(database);
ctx.buffer ctx.buffer
..write('INSERT ') ..write('INSERT ')
..write(replace ? 'OR REPLACE ' : '') ..write(replace ? 'OR REPLACE ' : '')

View File

@ -35,7 +35,7 @@ abstract class Query<T extends Table, DataClass> {
/// Constructs the query that can then be sent to the database executor. /// Constructs the query that can then be sent to the database executor.
@protected @protected
GenerationContext constructQuery() { GenerationContext constructQuery() {
final ctx = GenerationContext(database); final ctx = GenerationContext.fromDb(database);
var needsWhitespace = false; var needsWhitespace = false;
writeStartPart(ctx); writeStartPart(ctx);

View File

@ -117,7 +117,7 @@ class JoinedSelectStatement<FirstT extends Table, FirstD>
} }
Future<List<TypedResult>> _getWithQuery(GenerationContext ctx) async { Future<List<TypedResult>> _getWithQuery(GenerationContext ctx) async {
final results = await ctx.database.executor.doWhenOpened((e) async { final results = await ctx.executor.doWhenOpened((e) async {
return await e.runSelect(ctx.sql, ctx.boundVariables); return await e.runSelect(ctx.sql, ctx.boundVariables);
}); });
@ -162,7 +162,7 @@ class SimpleSelectStatement<T extends Table, D> extends Query<T, D>
} }
Future<List<D>> _getWithQuery(GenerationContext ctx) async { Future<List<D>> _getWithQuery(GenerationContext ctx) async {
final results = await ctx.database.executor.doWhenOpened((e) async { final results = await ctx.executor.doWhenOpened((e) async {
return await e.runSelect(ctx.sql, ctx.boundVariables); return await e.runSelect(ctx.sql, ctx.boundVariables);
}); });
return results.map(table.map).toList(); return results.map(table.map).toList();
@ -263,7 +263,7 @@ class CustomSelectStatement {
} }
List<dynamic> _mapArgs() { List<dynamic> _mapArgs() {
final ctx = GenerationContext(_db); final ctx = GenerationContext.fromDb(_db);
return variables.map((v) => v.mapToSimpleValue(ctx)).toList(); return variables.map((v) => v.mapToSimpleValue(ctx)).toList();
} }

View File

@ -32,7 +32,7 @@ class UpdateStatement<T extends Table, D> extends Query<T, D>
Future<int> _performQuery() async { Future<int> _performQuery() async {
final ctx = constructQuery(); final ctx = constructQuery();
final rows = await ctx.database.executor.doWhenOpened((e) async { final rows = await ctx.executor.doWhenOpened((e) async {
return await e.runUpdate(ctx.sql, ctx.boundVariables); return await e.runUpdate(ctx.sql, ctx.boundVariables);
}); });

View File

@ -36,31 +36,28 @@ abstract class GeneratedColumn<T, S extends SqlType<T>> extends Column<T, S> {
/// Writes the definition of this column, as defined /// Writes the definition of this column, as defined
/// [here](https://www.sqlite.org/syntax/column-def.html), into the given /// [here](https://www.sqlite.org/syntax/column-def.html), into the given
/// buffer. /// buffer.
void writeColumnDefinition(StringBuffer into) { void writeColumnDefinition(GenerationContext into) {
into.write('$escapedName $typeName '); into.buffer.write('$escapedName $typeName ');
if ($customConstraints == null) { if ($customConstraints == null) {
into.write($nullable ? 'NULL' : 'NOT NULL'); into.buffer.write($nullable ? 'NULL' : 'NOT NULL');
if (defaultValue != null) { if (defaultValue != null) {
into.write(' DEFAULT '); into.buffer.write(' DEFAULT ');
final fakeContext = GenerationContext(null);
defaultValue.writeInto(fakeContext);
// we need to write brackets if the default value is not a literal. // we need to write brackets if the default value is not a literal.
// see https://www.sqlite.org/syntax/column-constraint.html // see https://www.sqlite.org/syntax/column-constraint.html
final writeBrackets = !defaultValue.isLiteral; final writeBrackets = !defaultValue.isLiteral;
if (writeBrackets) into.write('('); if (writeBrackets) into.buffer.write('(');
into.write(fakeContext.sql); defaultValue.writeInto(into);
if (writeBrackets) into.write(')'); if (writeBrackets) into.buffer.write(')');
} }
// these custom constraints refer to builtin constraints from moor // these custom constraints refer to builtin constraints from moor
writeCustomConstraints(into); writeCustomConstraints(into.buffer);
} else { } else {
into.write($customConstraints); into.buffer.write($customConstraints);
} }
} }
@ -164,9 +161,10 @@ class GeneratedIntColumn extends GeneratedColumn<int, IntType>
$customConstraints: $customConstraints, defaultValue: defaultValue); $customConstraints: $customConstraints, defaultValue: defaultValue);
@override @override
void writeColumnDefinition(StringBuffer into) { void writeColumnDefinition(GenerationContext into) {
// todo make this work with custom constraints, default values, etc.
if (hasAutoIncrement) { if (hasAutoIncrement) {
into.write('${$name} $typeName PRIMARY KEY AUTOINCREMENT'); into.buffer.write('${$name} $typeName PRIMARY KEY AUTOINCREMENT');
} else { } else {
super.writeColumnDefinition(into); super.writeColumnDefinition(into);
} }

View File

@ -1,4 +1,5 @@
import 'package:moor/moor.dart'; import 'package:moor/moor.dart';
import 'package:moor/src/runtime/components/component.dart';
import 'package:test_api/test_api.dart'; import 'package:test_api/test_api.dart';
void main() { void main() {
@ -6,12 +7,12 @@ void main() {
final nonNull = GeneratedDateTimeColumn('name', null, false); final nonNull = GeneratedDateTimeColumn('name', null, false);
test('should write column definition', () { test('should write column definition', () {
final nullableBuff = StringBuffer(); final nonNullQuery = GenerationContext(null, null);
final nonNullBuff = StringBuffer(); final nullableQuery = GenerationContext(null, null);
nullable.writeColumnDefinition(nullableBuff); nonNull.writeColumnDefinition(nonNullQuery);
nonNull.writeColumnDefinition(nonNullBuff); nullable.writeColumnDefinition(nullableQuery);
expect(nullableBuff.toString(), equals('name INTEGER NULL')); expect(nullableQuery.sql, equals('name INTEGER NULL'));
expect(nonNullBuff.toString(), equals('name INTEGER NOT NULL')); expect(nonNullQuery.sql, equals('name INTEGER NOT NULL'));
}); });
} }

View File

@ -27,7 +27,7 @@ void main() {
comparisons.forEach((fn, value) { comparisons.forEach((fn, value) {
test('for operator $value', () { test('for operator $value', () {
final ctx = GenerationContext(db); final ctx = GenerationContext.fromDb(db);
fn(compare).writeInto(ctx); fn(compare).writeInto(ctx);
@ -39,7 +39,7 @@ void main() {
group('can compare with values', () { group('can compare with values', () {
comparisonsVal.forEach((fn, value) { comparisonsVal.forEach((fn, value) {
test('for operator $value', () { test('for operator $value', () {
final ctx = GenerationContext(db); final ctx = GenerationContext.fromDb(db);
fn(12).writeInto(ctx); fn(12).writeInto(ctx);

View File

@ -20,7 +20,7 @@ void main() {
expectedResults.forEach((key, value) { expectedResults.forEach((key, value) {
test('should extract field', () { test('should extract field', () {
final ctx = GenerationContext(null); final ctx = GenerationContext(const SqlTypeSystem.withDefaults(), null);
key(column).writeInto(ctx); key(column).writeInto(ctx);
expect(ctx.sql, value); expect(ctx.sql, value);

View File

@ -9,7 +9,7 @@ void main() {
final innerExpression = moor.GeneratedTextColumn('name', null, true); final innerExpression = moor.GeneratedTextColumn('name', null, true);
final isInExpression = moor.isIn(innerExpression, ['Max', 'Tobias']); final isInExpression = moor.isIn(innerExpression, ['Max', 'Tobias']);
final context = GenerationContext(TodoDb(null)); final context = GenerationContext.fromDb(TodoDb(null));
isInExpression.writeInto(context); isInExpression.writeInto(context);
expect(context.sql, 'name IN (?, ?)'); expect(context.sql, 'name IN (?, ?)');

View File

@ -10,7 +10,7 @@ void main() {
test('IS NULL expressions are generated', () { test('IS NULL expressions are generated', () {
final isNull = moor.isNull(innerExpression); final isNull = moor.isNull(innerExpression);
final context = GenerationContext(TodoDb(null)); final context = GenerationContext.fromDb(TodoDb(null));
isNull.writeInto(context); isNull.writeInto(context);
expect(context.sql, 'name IS NULL'); expect(context.sql, 'name IS NULL');
@ -19,7 +19,7 @@ void main() {
test('IS NOT NULL expressions are generated', () { test('IS NOT NULL expressions are generated', () {
final isNotNull = moor.isNotNull(innerExpression); final isNotNull = moor.isNotNull(innerExpression);
final context = GenerationContext(TodoDb(null)); final context = GenerationContext.fromDb(TodoDb(null));
isNotNull.writeInto(context); isNotNull.writeInto(context);
expect(context.sql, 'name IS NOT NULL'); expect(context.sql, 'name IS NOT NULL');

View File

@ -8,7 +8,7 @@ void main() {
test('maps the variable to sql', () { test('maps the variable to sql', () {
final variable = final variable =
Variable(DateTime.fromMillisecondsSinceEpoch(1551297563000)); Variable(DateTime.fromMillisecondsSinceEpoch(1551297563000));
final ctx = GenerationContext(TodoDb(null)); final ctx = GenerationContext.fromDb(TodoDb(null));
variable.writeInto(ctx); variable.writeInto(ctx);
@ -18,7 +18,7 @@ void main() {
test('writes null directly for null values', () { test('writes null directly for null values', () {
final variable = Variable.withString(null); final variable = Variable.withString(null);
final ctx = GenerationContext(TodoDb(null)); final ctx = GenerationContext.fromDb(TodoDb(null));
variable.writeInto(ctx); variable.writeInto(ctx);

View File

@ -7,7 +7,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.8" version: "2.1.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -45,7 +45,7 @@ packages:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.3+1" version: "0.12.5"
meta: meta:
dependency: "direct main" dependency: "direct main"
description: description:
@ -59,7 +59,7 @@ packages:
path: "../moor" path: "../moor"
relative: true relative: true
source: path source: path
version: "1.3.0" version: "1.3.1"
path: path:
dependency: "direct main" dependency: "direct main"
description: description:
@ -73,14 +73,14 @@ packages:
name: pedantic name: pedantic
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.4.0" version: "1.5.0"
quiver: quiver:
dependency: transitive dependency: transitive
description: description:
name: quiver name: quiver
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -92,7 +92,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.4" version: "1.5.5"
sqflite: sqflite:
dependency: "direct main" dependency: "direct main"
description: description:
@ -113,7 +113,7 @@ packages:
name: stream_channel name: stream_channel
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.6.8" version: "2.0.0"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
@ -141,7 +141,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.2" version: "0.2.4"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -157,4 +157,4 @@ packages:
source: hosted source: hosted
version: "2.0.8" version: "2.0.8"
sdks: sdks:
dart: ">=2.1.0 <3.0.0" dart: ">=2.2.0 <3.0.0"