Skip to content

Commit

Permalink
Added support for WITH statements for DatabaseViews (#443)
Browse files Browse the repository at this point in the history
  • Loading branch information
themadmrj authored Dec 10, 2020
1 parent 1a49c0f commit 9da21f3
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 1 deletion.
14 changes: 13 additions & 1 deletion floor_generator/lib/processor/view_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,21 @@ class ViewProcessor extends QueryableProcessor<View> {
.getField(AnnotationField.viewQuery)
?.toStringValue();

if (query == null || !query.trimLeft().toLowerCase().startsWith('select')) {
if (query == null || !(query.isSelectQuery || query.isCteWithSelect)) {
throw _processorError.missingQuery;
}
return query;
}
}

extension on String {
bool get isSelectQuery => toLowerCase().trimLeft().startsWith('select');

/// whether the string is a common table expression
/// followed by a `SELECT` query
bool get isCteWithSelect {
final lowerCasedString = toLowerCase();
return lowerCasedString.trimLeft().startsWith('with') &&
'select'.allMatches(lowerCasedString).length >= 2;
}
}
84 changes: 84 additions & 0 deletions floor_generator/test/processor/view_processor_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,90 @@ void main() {
expect(actual, equals(expected));
});

test('Process view starting with WITH statement', () async {
final classElement = await createClassElement('''
@DatabaseView("WITH subquery as (SELECT * from otherentity) SELECT subquery.*")
class Person {
final int id;
final String name;
Person(this.id, this.name);
}
''');

final actual = ViewProcessor(classElement, {}).process();

const name = 'Person';
final fields = classElement.fields
.map((fieldElement) => FieldProcessor(fieldElement, null).process())
.toList();
const query =
'WITH subquery as (SELECT * from otherentity) SELECT subquery.*';
const constructor = "Person(row['id'] as int, row['name'] as String)";
final expected = View(
classElement,
name,
fields,
query,
constructor,
);
expect(actual, equals(expected));
});
test('Throws when processing view without SELECT', () async {
final classElement = await createClassElement('''
@DatabaseView("DELETE all from Person")
class Person {
final int id;
final String name;
Person(this.id, this.name);
}
''');

final actual = () => ViewProcessor(classElement, {}).process();

expect(actual, throwsInvalidGenerationSourceError());
});

test(
'Throws when processing view starting with WITH statement without SELECT',
() async {
final classElement = await createClassElement('''
@DatabaseView("WITH subquery")
class Person {
final int id;
final String name;
Person(this.id, this.name);
}
''');

final actual = () => ViewProcessor(classElement, {}).process();

expect(actual, throwsInvalidGenerationSourceError());
});

test(
'Throws when processing view starting with WITH statement with only one SELECT',
() async {
final classElement = await createClassElement('''
@DatabaseView("WITH subquery as (SELECT * from otherentity)")
class Person {
final int id;
final String name;
Person(this.id, this.name);
}
''');

final actual = () => ViewProcessor(classElement, {}).process();

expect(actual, throwsInvalidGenerationSourceError());
});
test('Process view with mutliline query', () async {
final classElement = await createClassElement("""
@DatabaseView('''
Expand Down

0 comments on commit 9da21f3

Please sign in to comment.