diff --git a/moor/CHANGELOG.md b/moor/CHANGELOG.md index 59b6d7f1..0b7390db 100644 --- a/moor/CHANGELOG.md +++ b/moor/CHANGELOG.md @@ -8,7 +8,11 @@ ``` selectVariable(:variable AS TEXT): SELECT :variable; ``` -- Support for triggers! You can declare them in a `.moor` file and +- Support for triggers and indices! You can declare them in a `.moor` file with a regular `CREATE TRIGGER` + or `CREATE INDEX` statement. Both triggers and indices will be created in the default `onCreate` function. + To create them in `onUpgrade`, use the new `createIndex` and `createTrigger` functions on a `Migrator`. +- Support for moor-file queries that run on initialization ([#280](https://github.com/simolus3/moor/issues/280)) + Declare them like this `@create: INSERT INTO users VALUES ('default', 'user')` ## 2.2.0 diff --git a/sqlparser/lib/src/analysis/types2/graph/relationships.dart b/sqlparser/lib/src/analysis/types2/graph/relationships.dart index fd34fa28..2ed678ef 100644 --- a/sqlparser/lib/src/analysis/types2/graph/relationships.dart +++ b/sqlparser/lib/src/analysis/types2/graph/relationships.dart @@ -33,6 +33,11 @@ class HaveSameType extends TypeRelationship { final Typeable second; HaveSameType(this.first, this.second); + + Typeable getOther(Typeable t) { + assert(t == first || t == second); + return t == first ? second : first; + } } /// Dependency declaring that, if no better option is found, [target] should diff --git a/sqlparser/lib/src/analysis/types2/graph/type_graph.dart b/sqlparser/lib/src/analysis/types2/graph/type_graph.dart index ace3ce51..80680f34 100644 --- a/sqlparser/lib/src/analysis/types2/graph/type_graph.dart +++ b/sqlparser/lib/src/analysis/types2/graph/type_graph.dart @@ -2,6 +2,11 @@ part of '../types.dart'; class TypeGraph { final Map _knownTypes = {}; + final Map _knownNullability = {}; + + final List _relationships = []; + + final Map> _edges = {}; TypeGraph(); @@ -11,6 +16,80 @@ class TypeGraph { void operator []=(Typeable t, ResolvedType type) { _knownTypes[t] = type; + + if (type.nullable != null) { + // nullability is known + _knownNullability[t] = type.nullable; + } + } + + bool knowsType(Typeable t) => _knownTypes.containsKey(t); + + void addRelation(TypeRelationship relation) { + _relationships.add(relation); + } + + void performResolve() { + _indexRelationships(); + + final queue = List.of(_knownTypes.keys); + while (queue.isNotEmpty) { + _propagateTypeInfo(queue, queue.removeLast()); + } + } + + void _propagateTypeInfo(List resolved, Typeable t) { + if (!_edges.containsKey(t)) return; + + for (final edge in _edges[t]) { + if (edge is CopyTypeFrom) { + _copyType(resolved, edge.other, edge.target); + } else if (edge is HaveSameType) { + _copyType(resolved, t, edge.getOther(t)); + } + } + } + + void _copyType(List resolved, Typeable from, Typeable to) { + // if the target hasn't been resolved yet, copy the current type and + // visit the target later + if (!knowsType(to)) { + this[to] = this[from]; + resolved.add(to); + } + } + + void _indexRelationships() { + _edges.clear(); + + void put(Typeable t, TypeRelationship r) { + _edges.putIfAbsent(t, () => []).add(r); + } + + void putAll(Iterable t, TypeRelationship r) { + for (final element in t) { + put(element, r); + } + } + + for (final relation in _relationships) { + if (relation is NullableIfSomeOtherIs) { + putAll(relation.other, relation); + } else if (relation is CopyTypeFrom) { + put(relation.other, relation); + } else if (relation is CopyEncapsulating) { + putAll(relation.from, relation); + } else if (relation is HaveSameType) { + put(relation.first, relation); + put(relation.second, relation); + } else if (relation is DefaultType) { + put(relation.target, relation); + } else if (relation is CopyAndCast) { + put(relation.other, relation); + } else { + throw AssertionError('Unknown type relation: $relation'); + } + } } } diff --git a/sqlparser/lib/src/analysis/types2/resolving_visitor.dart b/sqlparser/lib/src/analysis/types2/resolving_visitor.dart index fd38a0a8..d2ca2ac5 100644 --- a/sqlparser/lib/src/analysis/types2/resolving_visitor.dart +++ b/sqlparser/lib/src/analysis/types2/resolving_visitor.dart @@ -5,8 +5,9 @@ class TypeResolver extends RecursiveVisitor { TypeResolver(this.session); - void start(AstNode root) { + void run(AstNode root) { visit(root, const NoTypeExpectation()); + session.finish(); } @override diff --git a/sqlparser/lib/src/analysis/types2/types.dart b/sqlparser/lib/src/analysis/types2/types.dart index 0fb1fd33..06060320 100644 --- a/sqlparser/lib/src/analysis/types2/types.dart +++ b/sqlparser/lib/src/analysis/types2/types.dart @@ -33,13 +33,17 @@ class TypeInferenceSession { return graph[t]; } - void addRelationship(TypeRelationship relationship) {} + void addRelationship(TypeRelationship relationship) { + graph.addRelation(relationship); + } void expectIsPossible(ResolvedType r, TypeExpectation expectation) {} void hintNullability(Typeable t, bool nullable) { assert(nullable != null); } + + void finish() {} } /// Keeps track of resolved variable types so that they can be re-used. diff --git a/sqlparser/test/analysis/types2/graph_test.dart b/sqlparser/test/analysis/types2/graph_test.dart new file mode 100644 index 00000000..a9e650de --- /dev/null +++ b/sqlparser/test/analysis/types2/graph_test.dart @@ -0,0 +1,19 @@ +import 'package:sqlparser/sqlparser.dart'; +import 'package:sqlparser/src/analysis/types2/types.dart'; +import 'package:test/test.dart'; + +class _FakeTypeable implements Typeable {} + +void main() { + test('copies types for a CopyTypeFrom relation', () { + final first = _FakeTypeable(); + final second = _FakeTypeable(); + + final graph = TypeGraph(); + graph[first] = const ResolvedType.bool(); + graph.addRelation(CopyTypeFrom(second, first)); + graph.performResolve(); + + expect(graph[second], const ResolvedType.bool()); + }); +} diff --git a/sqlparser/test/analysis/types2/resolver_test.dart b/sqlparser/test/analysis/types2/resolver_test.dart index 0466cd9d..9bd9b507 100644 --- a/sqlparser/test/analysis/types2/resolver_test.dart +++ b/sqlparser/test/analysis/types2/resolver_test.dart @@ -11,7 +11,7 @@ void main() { TypeResolver _obtainResolver(String sql) { final context = engine.analyze(sql); - return TypeResolver(TypeInferenceSession(context))..start(context.root); + return TypeResolver(TypeInferenceSession(context))..run(context.root); } ResolvedType _resolveFirstVariable(String sql) {