Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/raw query #517

Draft
wants to merge 7 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions example/lib/database.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions example/lib/task_dao.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@ abstract class TaskDao {
@Query('SELECT * FROM task')
Stream<List<Task>> findAllTasksAsStream();

@rawQuery
Stream<List<Task>> rawQueryTasksAsStream(SQLiteQuery query);

Stream<List<Task>> findYesterdaysTasksByMessageAsStream(String message) {
final timestamp = DateTime.now()
.subtract(
const Duration(days: 1),
)
.millisecondsSinceEpoch;
return rawQueryTasksAsStream(SQLiteQuery(
'SELECT * FROM task WHERE timestamp > ?1 AND message == ?2',
arguments: [
timestamp,
message,
]));
}

@insert
Future<void> insertTask(Task task);

Expand Down
1 change: 1 addition & 0 deletions floor/lib/floor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export 'package:floor/src/database.dart';
export 'package:floor/src/migration.dart';
export 'package:floor/src/sqflite_database_factory.dart';
export 'package:floor_annotation/floor_annotation.dart';
export 'package:floor_generator/value_object/sqlite_query.dart';
1 change: 1 addition & 0 deletions floor_annotation/lib/floor_annotation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export 'src/insert.dart';
export 'src/on_conflict_strategy.dart';
export 'src/primary_key.dart';
export 'src/query.dart';
export 'src/raw_query.dart';
export 'src/transaction.dart';
export 'src/type_converter.dart';
export 'src/type_converters.dart';
Expand Down
5 changes: 5 additions & 0 deletions floor_annotation/lib/src/raw_query.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class RawQuery {
const RawQuery();
}

const rawQuery = RawQuery();
16 changes: 16 additions & 0 deletions floor_generator/lib/misc/type_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:typed_data';
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:floor_generator/value_object/sqlite_query.dart';
import 'package:source_gen/source_gen.dart';

extension SupportedTypeChecker on DartType {
Expand All @@ -19,6 +20,7 @@ extension SupportedTypeChecker on DartType {
_intTypeChecker,
_doubleTypeChecker,
_uint8ListTypeChecker,
_sqliteQueryTypeChecker,
]).isExactlyType(this);
}
}
Expand All @@ -28,6 +30,11 @@ extension Uint8ListTypeChecker on DartType {
getDisplayString(withNullability: false) == 'Uint8List';
}

extension SQLiteQueryTypeChecker on DartType {
bool get isSQLiteQuery =>
getDisplayString(withNullability: false) == 'SQLiteQuery';
}

extension StreamTypeChecker on DartType {
bool get isStream => _streamTypeChecker.isExactlyType(this);
}
Expand All @@ -43,6 +50,13 @@ extension AnnotationChecker on Element {
return _typeChecker(type).hasAnnotationOfExact(this);
}

bool containsAnnotation(final List<Type> types) {
return types.firstWhere(
(type) => _typeChecker(type).hasAnnotationOfExact(this),
orElse: () => null.runtimeType) !=
null.runtimeType;
}

/// Returns the first annotation object found of [type]
/// or `null` if annotation of [type] not found
DartObject? getAnnotation(final Type type) {
Expand All @@ -63,3 +77,5 @@ final _doubleTypeChecker = _typeChecker(double);
final _uint8ListTypeChecker = _typeChecker(Uint8List);

final _streamTypeChecker = _typeChecker(Stream);

final _sqliteQueryTypeChecker = _typeChecker(SQLiteQuery);
108 changes: 108 additions & 0 deletions floor_generator/lib/processor/base_query_method_processor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:collection/collection.dart';
import 'package:floor_generator/misc/extension/dart_type_extension.dart';
import 'package:floor_generator/misc/type_utils.dart';
import 'package:floor_generator/processor/error/base_query_method_processor_error.dart';
import 'package:floor_generator/processor/processor.dart';
import 'package:floor_generator/value_object/query_method.dart';
import 'package:floor_generator/value_object/queryable.dart';

abstract class BaseQueryMethodProcessor extends Processor<QueryMethod> {
final MethodElement _methodElement;
final List<Queryable> _queryables;
final BaseQueryMethodProcessorError _processorError;

BaseQueryMethodProcessor(
final MethodElement methodElement,
final List<Queryable> queryables,
) : _methodElement = methodElement,
_queryables = queryables,
_processorError = BaseQueryMethodProcessorError(methodElement);

@override
QueryMethod process() {
final name = _methodElement.displayName;
final parameters = _methodElement.parameters;
final returnType = _methodElement.returnType;
final returnsStream = returnType.isStream;
_assertReturnsFutureOrStream(returnType, returnsStream);
final returnsList = _getReturnsList(returnType, returnsStream);

final flattenedReturnType = _getFlattenedReturnType(
returnType,
returnsStream,
returnsList,
);

_assertReturnsNullableSingle(
returnsStream,
returnsList,
flattenedReturnType,
);

final queryable = _queryables.firstWhereOrNull((queryable) =>
queryable.classElement.displayName ==
flattenedReturnType.getDisplayString(withNullability: false));

return onProcess(
_methodElement,
name,
returnType,
flattenedReturnType,
parameters,
queryable,
);
}

DartType _getFlattenedReturnType(
final DartType rawReturnType,
final bool returnsStream,
final bool returnsList,
) {
final type = returnsStream
? _methodElement.returnType.flatten()
: _methodElement.library.typeSystem.flatten(rawReturnType);
return returnsList ? type.flatten() : type;
}

bool _getReturnsList(final DartType returnType, final bool returnsStream) {
final type = returnsStream
? returnType.flatten()
: _methodElement.library.typeSystem.flatten(returnType);

return type.isDartCoreList;
}

void _assertReturnsFutureOrStream(
final DartType rawReturnType,
final bool returnsStream,
) {
if (!rawReturnType.isDartAsyncFuture && !returnsStream) {
throw _processorError.doesNotReturnFutureNorStream;
}
}

void _assertReturnsNullableSingle(
final bool returnsStream,
final bool returnsList,
final DartType flattenedReturnType,
) {
if (!returnsList &&
!flattenedReturnType.isVoid &&
!flattenedReturnType.isNullable) {
returnsStream
? throw _processorError.doesNotReturnNullableStream
: throw _processorError.doesNotReturnNullableFuture;
}
}

QueryMethod onProcess(
MethodElement methodElement,
String name,
DartType returnType,
DartType flattenedReturnType,
List<ParameterElement> parameters,
Queryable? queryable,
);
}
26 changes: 19 additions & 7 deletions floor_generator/lib/processor/dao_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:floor_generator/processor/deletion_method_processor.dart';
import 'package:floor_generator/processor/insertion_method_processor.dart';
import 'package:floor_generator/processor/processor.dart';
import 'package:floor_generator/processor/query_method_processor.dart';
import 'package:floor_generator/processor/raw_query_method_processor.dart';
import 'package:floor_generator/processor/transaction_method_processor.dart';
import 'package:floor_generator/processor/update_method_processor.dart';
import 'package:floor_generator/value_object/dao.dart';
Expand Down Expand Up @@ -83,13 +84,24 @@ class DaoProcessor extends Processor<Dao> {
final Set<TypeConverter> typeConverters,
) {
return methods
.where((method) => method.hasAnnotation(annotations.Query))
.map((method) => QueryMethodProcessor(
method,
[..._entities, ..._views],
typeConverters,
).process())
.toList();
.where((method) => method.containsAnnotation([
annotations.Query,
annotations.RawQuery,
]))
.map((method) {
if (method.hasAnnotation(annotations.Query)) {
return QueryMethodProcessor(
method,
[..._entities, ..._views],
typeConverters,
).process();
} else {
return RawQueryMethodProcessor(
method,
[..._entities, ..._views],
).process();
}
}).toList();
}

List<InsertionMethod> _getInsertionMethods(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:floor_generator/processor/error/processor_error.dart';
import 'package:source_gen/source_gen.dart';

class BaseQueryMethodProcessorError {
final MethodElement _methodElement;

BaseQueryMethodProcessorError(final MethodElement methodElement)
: _methodElement = methodElement;

InvalidGenerationSourceError get doesNotReturnFutureNorStream {
return InvalidGenerationSourceError(
'All queries have to return a Future or Stream.',
todo: 'Define the return type as Future or Stream.',
element: _methodElement,
);
}

ProcessorError get doesNotReturnNullableStream {
return ProcessorError(
message: 'Queries returning streams of single elements might emit null.',
todo:
'Make the method return a Stream of a nullable type e.g. Stream<Person?>.',
element: _methodElement,
);
}

ProcessorError get doesNotReturnNullableFuture {
return ProcessorError(
message: 'Queries returning single elements might return null.',
todo:
'Make the method return a Future of a nullable type e.g. Future<Person?>.',
element: _methodElement,
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:floor_generator/processor/error/processor_error.dart';
import 'package:source_gen/source_gen.dart';

class QueryMethodProcessorError {
Expand All @@ -23,22 +22,4 @@ class QueryMethodProcessorError {
element: _methodElement,
);
}

ProcessorError get doesNotReturnNullableStream {
return ProcessorError(
message: 'Queries returning streams of single elements might emit null.',
todo:
'Make the method return a Stream of a nullable type e.g. Stream<Person?>.',
element: _methodElement,
);
}

ProcessorError get doesNotReturnNullableFuture {
return ProcessorError(
message: 'Queries returning single elements might return null.',
todo:
'Make the method return a Future of a nullable type e.g. Future<Person?>.',
element: _methodElement,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:floor_generator/processor/error/processor_error.dart';
import 'package:source_gen/source_gen.dart';

class RawQueryMethodProcessorError {
final MethodElement _methodElement;

RawQueryMethodProcessorError(final MethodElement methodElement)
: _methodElement = methodElement;

InvalidGenerationSourceError get queryArgumentsShouldBeSingle {
return InvalidGenerationSourceError(
'RawQuery methods should have 1 and only 1 parameter with type String or SQLiteQuery',
todo: 'Make sure to supply one parameter per SQL query argument.',
element: _methodElement,
);
}

ProcessorError queryMethodParameterIsNullable(
final ParameterElement parameterElement,
) {
return ProcessorError(
message: 'Query method parameters have to be non-nullable.',
todo: 'Define ${parameterElement.displayName} as non-nullable.'
'\nIf you want to assert null, change your query to use the `IS NULL`/'
'`IS NOT NULL` operator without passing a nullable parameter.',
element: _methodElement,
);
}
}
2 changes: 1 addition & 1 deletion floor_generator/lib/processor/field_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ extension on DartType {
return SqlType.integer;
} else if (isDartCoreDouble) {
return SqlType.real;
} else if (isUint8List) {
} else if (isUint8List || isSQLiteQuery) {
return SqlType.blob;
}
throw StateError('This should really be unreachable');
Expand Down
Loading