mirror of https://github.com/AMT-Cheif/drift.git
Merge remote-tracking branch 'upstream/develop' into column-name-case
This commit is contained in:
commit
b08088ab1b
|
@ -143,6 +143,37 @@ We currently support the following extensions:
|
|||
module is available. Note that this is not the case for most sqlite3 builds,
|
||||
including the ones shipping with `sqlite3_flutter_libs`.
|
||||
|
||||
### Known custom functions
|
||||
|
||||
The `modules` options can be used to tell drift's analyzer that a well-known
|
||||
sqlite3 extension is available at runtime. In some backends (like a `NativeDatabase`),
|
||||
it is also possible to specify entirely custom functions.
|
||||
|
||||
To be able to use these functions in `.drift` files, you can tell drift's
|
||||
analyzer about them. To do so, add a `known_functions` block to the options:
|
||||
|
||||
```yaml
|
||||
targets:
|
||||
$default:
|
||||
builders:
|
||||
drift_dev:
|
||||
options:
|
||||
sql:
|
||||
dialect: sqlite
|
||||
options:
|
||||
known_functions:
|
||||
my_function: "boolean (text, int null)"
|
||||
```
|
||||
|
||||
With these options, drift will analyze queries under the assumption that a SQL
|
||||
function called `my_function` taking a non-nullable textual value an a nullable
|
||||
integer will return a non-null value that drift can interpret as a boolean.
|
||||
|
||||
The syntax for a function type is defined as `<return type> (<argument types>)`.
|
||||
Each type consists of an arbitrary word used to determine [column affinity](https://www.sqlite.org/datatype3.html#determination_of_column_affinity),
|
||||
with drift also supporting `DATETIME` and `BOOLEAN` as type hints. Then, the
|
||||
optional `NULL` keyword can be used to indicate whether the type is nullable.
|
||||
|
||||
## Recommended options
|
||||
|
||||
In general, we recommend using the default options.
|
||||
|
|
|
@ -8,7 +8,7 @@ environment:
|
|||
dependencies:
|
||||
drift:
|
||||
path: ^1.8.2
|
||||
json_annotation: ^4.6.0
|
||||
json_annotation: ^4.7.0
|
||||
docsy:
|
||||
hosted: https://simonbinder.eu
|
||||
version: ^0.2.2
|
||||
|
|
|
@ -148,22 +148,10 @@ class DriftCommunication {
|
|||
/// [handler] returns a [Future], it will be awaited.
|
||||
void setRequestHandler(dynamic Function(Request) handler) {
|
||||
incomingRequests.listen((request) {
|
||||
try {
|
||||
final result = handler(request);
|
||||
|
||||
if (result is Future) {
|
||||
result.then(
|
||||
(value) => respond(request, value),
|
||||
onError: (e, StackTrace s) {
|
||||
respondError(request, e, s);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
respond(request, result);
|
||||
}
|
||||
} catch (e, s) {
|
||||
respondError(request, e, s);
|
||||
}
|
||||
Future.sync(() => handler(request)).then(
|
||||
(result) => respond(request, result),
|
||||
onError: (Object e, StackTrace s) => respondError(request, e, s),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,7 +83,8 @@ mixin JsonTypeConverter<D, S> implements JsonTypeConverter2<D, S, S> {
|
|||
|
||||
/// Implementation for an enum to int converter that uses the index of the enum
|
||||
/// as the value stored in the database.
|
||||
class EnumIndexConverter<T extends Enum> extends TypeConverter<T, int> {
|
||||
class EnumIndexConverter<T extends Enum> extends TypeConverter<T, int>
|
||||
with JsonTypeConverter<T, int> {
|
||||
/// All values of the enum.
|
||||
final List<T> values;
|
||||
|
||||
|
@ -103,7 +104,8 @@ class EnumIndexConverter<T extends Enum> extends TypeConverter<T, int> {
|
|||
|
||||
/// Implementation for an enum to string converter that uses the name of the
|
||||
/// enum as the value stored in the database.
|
||||
class EnumNameConverter<T extends Enum> extends TypeConverter<T, String> {
|
||||
class EnumNameConverter<T extends Enum> extends TypeConverter<T, String>
|
||||
with JsonTypeConverter<T, String> {
|
||||
/// All values of the enum.
|
||||
final List<T> values;
|
||||
|
||||
|
|
|
@ -548,8 +548,8 @@ class Config extends DataClass implements Insertable<Config> {
|
|||
configKey: serializer.fromJson<String>(json['config_key']),
|
||||
configValue: serializer.fromJson<String?>(json['config_value']),
|
||||
syncState: serializer.fromJson<SyncType?>(json['sync_state']),
|
||||
syncStateImplicit:
|
||||
serializer.fromJson<SyncType?>(json['sync_state_implicit']),
|
||||
syncStateImplicit: ConfigTable.$convertersyncStateImplicitn
|
||||
.fromJson(serializer.fromJson<int?>(json['sync_state_implicit'])),
|
||||
);
|
||||
}
|
||||
factory Config.fromJsonString(String encodedJson,
|
||||
|
@ -563,7 +563,8 @@ class Config extends DataClass implements Insertable<Config> {
|
|||
'config_key': serializer.toJson<String>(configKey),
|
||||
'config_value': serializer.toJson<String?>(configValue),
|
||||
'sync_state': serializer.toJson<SyncType?>(syncState),
|
||||
'sync_state_implicit': serializer.toJson<SyncType?>(syncStateImplicit),
|
||||
'sync_state_implicit': serializer.toJson<int?>(
|
||||
ConfigTable.$convertersyncStateImplicitn.toJson(syncStateImplicit)),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -774,10 +775,11 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
|
|||
const SyncTypeConverter();
|
||||
static TypeConverter<SyncType?, int?> $convertersyncStaten =
|
||||
NullAwareTypeConverter.wrap($convertersyncState);
|
||||
static TypeConverter<SyncType, int> $convertersyncStateImplicit =
|
||||
static JsonTypeConverter2<SyncType, int, int> $convertersyncStateImplicit =
|
||||
const EnumIndexConverter<SyncType>(SyncType.values);
|
||||
static TypeConverter<SyncType?, int?> $convertersyncStateImplicitn =
|
||||
NullAwareTypeConverter.wrap($convertersyncStateImplicit);
|
||||
static JsonTypeConverter2<SyncType?, int?, int?>
|
||||
$convertersyncStateImplicitn =
|
||||
JsonTypeConverter2.asNullable($convertersyncStateImplicit);
|
||||
@override
|
||||
bool get isStrict => true;
|
||||
@override
|
||||
|
@ -1472,8 +1474,8 @@ class MyViewData extends DataClass {
|
|||
configKey: serializer.fromJson<String>(json['config_key']),
|
||||
configValue: serializer.fromJson<String?>(json['config_value']),
|
||||
syncState: serializer.fromJson<SyncType?>(json['sync_state']),
|
||||
syncStateImplicit:
|
||||
serializer.fromJson<SyncType?>(json['sync_state_implicit']),
|
||||
syncStateImplicit: ConfigTable.$convertersyncStateImplicitn
|
||||
.fromJson(serializer.fromJson<int?>(json['sync_state_implicit'])),
|
||||
);
|
||||
}
|
||||
factory MyViewData.fromJsonString(String encodedJson,
|
||||
|
@ -1488,7 +1490,8 @@ class MyViewData extends DataClass {
|
|||
'config_key': serializer.toJson<String>(configKey),
|
||||
'config_value': serializer.toJson<String?>(configValue),
|
||||
'sync_state': serializer.toJson<SyncType?>(syncState),
|
||||
'sync_state_implicit': serializer.toJson<SyncType?>(syncStateImplicit),
|
||||
'sync_state_implicit': serializer.toJson<int?>(
|
||||
ConfigTable.$convertersyncStateImplicitn.toJson(syncStateImplicit)),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,8 @@ class Category extends DataClass implements Insertable<Category> {
|
|||
return Category(
|
||||
id: serializer.fromJson<int>(json['id']),
|
||||
description: serializer.fromJson<String>(json['description']),
|
||||
priority: serializer.fromJson<CategoryPriority>(json['priority']),
|
||||
priority: $CategoriesTable.$converterpriority
|
||||
.fromJson(serializer.fromJson<int>(json['priority'])),
|
||||
descriptionInUpperCase:
|
||||
serializer.fromJson<String>(json['descriptionInUpperCase']),
|
||||
);
|
||||
|
@ -55,7 +56,8 @@ class Category extends DataClass implements Insertable<Category> {
|
|||
return <String, dynamic>{
|
||||
'id': serializer.toJson<int>(id),
|
||||
'description': serializer.toJson<String>(description),
|
||||
'priority': serializer.toJson<CategoryPriority>(priority),
|
||||
'priority': serializer
|
||||
.toJson<int>($CategoriesTable.$converterpriority.toJson(priority)),
|
||||
'descriptionInUpperCase':
|
||||
serializer.toJson<String>(descriptionInUpperCase),
|
||||
};
|
||||
|
@ -256,7 +258,7 @@ class $CategoriesTable extends Categories
|
|||
return $CategoriesTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static TypeConverter<CategoryPriority, int> $converterpriority =
|
||||
static JsonTypeConverter2<CategoryPriority, int, int> $converterpriority =
|
||||
const EnumIndexConverter<CategoryPriority>(CategoryPriority.values);
|
||||
}
|
||||
|
||||
|
@ -321,7 +323,8 @@ class TodoEntry extends DataClass implements Insertable<TodoEntry> {
|
|||
content: serializer.fromJson<String>(json['content']),
|
||||
targetDate: serializer.fromJson<DateTime?>(json['target_date']),
|
||||
category: serializer.fromJson<int?>(json['category']),
|
||||
status: serializer.fromJson<TodoStatus?>(json['status']),
|
||||
status: $TodosTableTable.$converterstatusn
|
||||
.fromJson(serializer.fromJson<String?>(json['status'])),
|
||||
);
|
||||
}
|
||||
factory TodoEntry.fromJsonString(String encodedJson,
|
||||
|
@ -338,7 +341,8 @@ class TodoEntry extends DataClass implements Insertable<TodoEntry> {
|
|||
'content': serializer.toJson<String>(content),
|
||||
'target_date': serializer.toJson<DateTime?>(targetDate),
|
||||
'category': serializer.toJson<int?>(category),
|
||||
'status': serializer.toJson<TodoStatus?>(status),
|
||||
'status': serializer
|
||||
.toJson<String?>($TodosTableTable.$converterstatusn.toJson(status)),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -605,10 +609,10 @@ class $TodosTableTable extends TodosTable
|
|||
return $TodosTableTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static TypeConverter<TodoStatus, String> $converterstatus =
|
||||
static JsonTypeConverter2<TodoStatus, String, String> $converterstatus =
|
||||
const EnumNameConverter<TodoStatus>(TodoStatus.values);
|
||||
static TypeConverter<TodoStatus?, String?> $converterstatusn =
|
||||
NullAwareTypeConverter.wrap($converterstatus);
|
||||
static JsonTypeConverter2<TodoStatus?, String?, String?> $converterstatusn =
|
||||
JsonTypeConverter2.asNullable($converterstatus);
|
||||
}
|
||||
|
||||
class User extends DataClass implements Insertable<User> {
|
||||
|
@ -1308,7 +1312,7 @@ class PureDefault extends DataClass implements Insertable<PureDefault> {
|
|||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return PureDefault(
|
||||
txt: $PureDefaultsTable.$convertertxtn
|
||||
.fromJson(serializer.fromJson<Map<dynamic, dynamic>>(json['txt'])),
|
||||
.fromJson(serializer.fromJson<Map<dynamic, dynamic>?>(json['txt'])),
|
||||
);
|
||||
}
|
||||
factory PureDefault.fromJsonString(String encodedJson,
|
||||
|
|
|
@ -20,7 +20,7 @@ final Map<String, dynamic> _regularSerialized = {
|
|||
'content': 'content',
|
||||
'target_date': _someDate.millisecondsSinceEpoch,
|
||||
'category': 3,
|
||||
'status': TodoStatus.open,
|
||||
'status': TodoStatus.open.name,
|
||||
};
|
||||
|
||||
final Map<String, dynamic> _asTextSerialized = {
|
||||
|
@ -29,7 +29,7 @@ final Map<String, dynamic> _asTextSerialized = {
|
|||
'content': 'content',
|
||||
'target_date': _someDate.toIso8601String(),
|
||||
'category': 3,
|
||||
'status': TodoStatus.open,
|
||||
'status': TodoStatus.open.name,
|
||||
};
|
||||
|
||||
final Map<String, dynamic> _customSerialized = {
|
||||
|
@ -38,7 +38,7 @@ final Map<String, dynamic> _customSerialized = {
|
|||
'content': 'content',
|
||||
'target_date': _someDate.toIso8601String(),
|
||||
'category': 3,
|
||||
'status': TodoStatus.open,
|
||||
'status': TodoStatus.open.name,
|
||||
};
|
||||
|
||||
class CustomSerializer extends ValueSerializer {
|
||||
|
|
|
@ -9,6 +9,7 @@ import '../resolver/dart/helper.dart';
|
|||
import '../resolver/discover.dart';
|
||||
import '../resolver/drift/sqlparser/mapping.dart';
|
||||
import '../resolver/file_analysis.dart';
|
||||
import '../resolver/queries/custom_known_functions.dart';
|
||||
import '../resolver/resolver.dart';
|
||||
import '../results/results.dart';
|
||||
import '../serializer.dart';
|
||||
|
@ -69,6 +70,7 @@ class DriftAnalysisDriver {
|
|||
EngineOptions(
|
||||
useDriftExtensions: true,
|
||||
enabledExtensions: [
|
||||
DriftOptionsExtension(options),
|
||||
if (options.hasModule(SqlModule.fts5)) const Fts5Extension(),
|
||||
if (options.hasModule(SqlModule.json1)) const Json1Extension(),
|
||||
if (options.hasModule(SqlModule.moor_ffi))
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import 'package:charcode/ascii.dart';
|
||||
import 'package:drift/drift.dart' show SqlDialect;
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:sqlparser/sqlparser.dart' show SqliteVersion;
|
||||
import 'package:sqlparser/sqlparser.dart'
|
||||
show BasicType, ResolvedType, SchemaFromCreateTable, SqliteVersion;
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
|
||||
part '../generated/analysis/options.g.dart';
|
||||
|
||||
|
@ -206,7 +209,13 @@ class SqliteAnalysisOptions {
|
|||
@_SqliteVersionConverter()
|
||||
final SqliteVersion? version;
|
||||
|
||||
const SqliteAnalysisOptions({this.modules = const [], this.version});
|
||||
final Map<String, KnownSqliteFunction> knownFunctions;
|
||||
|
||||
const SqliteAnalysisOptions({
|
||||
this.modules = const [],
|
||||
this.version,
|
||||
this.knownFunctions = const {},
|
||||
});
|
||||
|
||||
factory SqliteAnalysisOptions.fromJson(Map json) {
|
||||
return _$SqliteAnalysisOptionsFromJson(json);
|
||||
|
@ -215,6 +224,73 @@ class SqliteAnalysisOptions {
|
|||
Map<String, Object?> toJson() => _$SqliteAnalysisOptionsToJson(this);
|
||||
}
|
||||
|
||||
class KnownSqliteFunction {
|
||||
final List<ResolvedType> argumentTypes;
|
||||
final ResolvedType returnType;
|
||||
|
||||
KnownSqliteFunction(this.argumentTypes, this.returnType);
|
||||
|
||||
factory KnownSqliteFunction.fromJson(String json) {
|
||||
final scanner = StringScanner(json);
|
||||
final types = SchemaFromCreateTable(driftExtensions: true);
|
||||
|
||||
ResolvedType parseType() {
|
||||
scanner.scan(_whitespace);
|
||||
scanner.expect(_word, name: 'Type name');
|
||||
final type = types.resolveColumnType(scanner.lastMatch?.group(0));
|
||||
|
||||
return type.copyWith(nullable: scanner.scan(_null));
|
||||
}
|
||||
|
||||
final argumentTypes = <ResolvedType>[];
|
||||
final returnType = parseType();
|
||||
|
||||
scanner
|
||||
..scan(_whitespace)
|
||||
..expectChar($openParen)
|
||||
..scan(_whitespace);
|
||||
|
||||
if (scanner.peekChar() != $closeParen) {
|
||||
argumentTypes.add(parseType());
|
||||
while (scanner.scanChar($comma)) {
|
||||
argumentTypes.add(parseType());
|
||||
}
|
||||
}
|
||||
|
||||
scanner
|
||||
..scan(_whitespace)
|
||||
..expectChar($closeParen)
|
||||
..scan(_whitespace)
|
||||
..expectDone();
|
||||
|
||||
return KnownSqliteFunction(argumentTypes, returnType);
|
||||
}
|
||||
|
||||
String toJson() {
|
||||
String toString(ResolvedType type) {
|
||||
switch (type.type!) {
|
||||
case BasicType.nullType:
|
||||
return 'NULL';
|
||||
case BasicType.int:
|
||||
return 'INTEGER';
|
||||
case BasicType.real:
|
||||
return 'REAL';
|
||||
case BasicType.text:
|
||||
return 'TEXT';
|
||||
case BasicType.blob:
|
||||
return 'BLOB';
|
||||
}
|
||||
}
|
||||
|
||||
final types = argumentTypes.map(toString).join(', ');
|
||||
return '${toString(returnType)}($types)';
|
||||
}
|
||||
|
||||
static final _word = RegExp(r'\w+');
|
||||
static final _null = RegExp(r'\s+null', caseSensitive: false);
|
||||
static final _whitespace = RegExp(r'\s*');
|
||||
}
|
||||
|
||||
class _SqliteVersionConverter extends JsonConverter<SqliteVersion, String> {
|
||||
static final _versionRegex = RegExp(r'(\d+)\.(\d+)');
|
||||
|
||||
|
|
|
@ -341,6 +341,7 @@ class ColumnParser {
|
|||
_resolver.resolver.driver.options.columnNameCase
|
||||
.apply(getter.name.lexeme);
|
||||
final sqlType = _startMethodToColumnType(foundStartMethod);
|
||||
final helper = await _resolver.resolver.driver.loadKnownTypes();
|
||||
|
||||
AppliedTypeConverter? converter;
|
||||
if (mappedAs != null) {
|
||||
|
@ -351,7 +352,7 @@ class ColumnParser {
|
|||
nullable,
|
||||
(message) => _resolver.reportError(
|
||||
DriftAnalysisError.inDartAst(element, mappedAs!, message)),
|
||||
await _resolver.resolver.driver.loadKnownTypes(),
|
||||
helper,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -370,6 +371,7 @@ class ColumnParser {
|
|||
remainingExpr.typeArguments ?? remainingExpr.methodName, msg)),
|
||||
enumType,
|
||||
EnumType.intEnum,
|
||||
helper,
|
||||
);
|
||||
} else if (foundStartMethod == _startTextEnum) {
|
||||
if (converter != null) {
|
||||
|
@ -386,6 +388,7 @@ class ColumnParser {
|
|||
remainingExpr.typeArguments ?? remainingExpr.methodName, msg)),
|
||||
enumType,
|
||||
EnumType.textEnum,
|
||||
helper,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
|
|||
Table table;
|
||||
final references = <DriftElement>{};
|
||||
final stmt = discovered.sqlNode;
|
||||
final helper = await resolver.driver.loadKnownTypes();
|
||||
|
||||
try {
|
||||
final reader = SchemaFromCreateTable(
|
||||
|
@ -74,6 +75,7 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
|
|||
DriftAnalysisError.inDriftFile(column.definition ?? stmt, msg)),
|
||||
dartClass.classElement.thisType,
|
||||
type == DriftSqlType.int ? EnumType.intEnum : EnumType.textEnum,
|
||||
helper,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import 'package:sqlparser/sqlparser.dart';
|
||||
|
||||
import '../../options.dart';
|
||||
|
||||
class DriftOptionsExtension implements Extension {
|
||||
final DriftOptions options;
|
||||
|
||||
DriftOptionsExtension(this.options);
|
||||
|
||||
@override
|
||||
void register(SqlEngine engine) {
|
||||
final knownFunctions = options.sqliteAnalysisOptions?.knownFunctions;
|
||||
|
||||
if (knownFunctions != null) {
|
||||
engine.registerFunctionHandler(_CustomFunctions(knownFunctions));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomFunctions extends FunctionHandler {
|
||||
// always has lowercase keys
|
||||
final Map<String, KnownSqliteFunction> _functions;
|
||||
|
||||
_CustomFunctions(Map<String, KnownSqliteFunction> functions)
|
||||
: _functions = {
|
||||
for (final function in functions.entries)
|
||||
function.key.toLowerCase(): function.value,
|
||||
};
|
||||
|
||||
@override
|
||||
late final Set<String> functionNames = _functions.keys.toSet();
|
||||
|
||||
@override
|
||||
ResolveResult inferArgumentType(
|
||||
AnalysisContext context, SqlInvocation call, Expression argument) {
|
||||
final types = _functions[call.name.toLowerCase()]?.argumentTypes;
|
||||
if (types == null) {
|
||||
return const ResolveResult.unknown();
|
||||
}
|
||||
|
||||
final parameters = call.parameters;
|
||||
if (parameters is ExprFunctionParameters) {
|
||||
final index = parameters.parameters.indexOf(argument);
|
||||
|
||||
if (index < types.length) {
|
||||
return ResolveResult(types[index]);
|
||||
}
|
||||
}
|
||||
|
||||
return const ResolveResult.unknown();
|
||||
}
|
||||
|
||||
@override
|
||||
ResolveResult inferReturnType(AnalysisContext context, SqlInvocation call,
|
||||
List<Typeable> expandedArgs) {
|
||||
final type = _functions[call.name.toLowerCase()]?.returnType;
|
||||
|
||||
return type != null ? ResolveResult(type) : const ResolveResult.unknown();
|
||||
}
|
||||
}
|
|
@ -230,7 +230,9 @@ AppliedTypeConverter readEnumConverter(
|
|||
void Function(String) reportError,
|
||||
DartType dartEnumType,
|
||||
EnumType columnEnumType,
|
||||
KnownDriftTypes helper,
|
||||
) {
|
||||
final typeProvider = helper.helperLibrary.typeProvider;
|
||||
if (dartEnumType is! InterfaceType) {
|
||||
reportError('Not a class: `$dartEnumType`');
|
||||
}
|
||||
|
@ -261,7 +263,9 @@ AppliedTypeConverter readEnumConverter(
|
|||
return AppliedTypeConverter(
|
||||
expression: expression,
|
||||
dartType: dartEnumType,
|
||||
jsonType: null,
|
||||
jsonType: columnEnumType == EnumType.intEnum
|
||||
? typeProvider.intType
|
||||
: typeProvider.stringType,
|
||||
sqlType: columnEnumType == EnumType.intEnum
|
||||
? DriftSqlType.int
|
||||
: DriftSqlType.string,
|
||||
|
|
|
@ -201,7 +201,7 @@ SqliteAnalysisOptions _$SqliteAnalysisOptionsFromJson(Map json) =>
|
|||
($checkedConvert) {
|
||||
$checkKeys(
|
||||
json,
|
||||
allowedKeys: const ['modules', 'version'],
|
||||
allowedKeys: const ['modules', 'version', 'known_functions'],
|
||||
);
|
||||
final val = SqliteAnalysisOptions(
|
||||
modules: $checkedConvert(
|
||||
|
@ -215,9 +215,18 @@ SqliteAnalysisOptions _$SqliteAnalysisOptionsFromJson(Map json) =>
|
|||
'version',
|
||||
(v) => _$JsonConverterFromJson<String, SqliteVersion>(
|
||||
v, const _SqliteVersionConverter().fromJson)),
|
||||
knownFunctions: $checkedConvert(
|
||||
'known_functions',
|
||||
(v) =>
|
||||
(v as Map?)?.map(
|
||||
(k, e) => MapEntry(
|
||||
k as String, KnownSqliteFunction.fromJson(e as String)),
|
||||
) ??
|
||||
const {}),
|
||||
);
|
||||
return val;
|
||||
},
|
||||
fieldKeyMap: const {'knownFunctions': 'known_functions'},
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SqliteAnalysisOptionsToJson(
|
||||
|
@ -226,6 +235,8 @@ Map<String, dynamic> _$SqliteAnalysisOptionsToJson(
|
|||
'modules': instance.modules.map((e) => _$SqlModuleEnumMap[e]!).toList(),
|
||||
'version': _$JsonConverterToJson<String, SqliteVersion>(
|
||||
instance.version, const _SqliteVersionConverter().toJson),
|
||||
'known_functions':
|
||||
instance.knownFunctions.map((k, e) => MapEntry(k, e.toJson())),
|
||||
};
|
||||
|
||||
Value? _$JsonConverterFromJson<Json, Value>(
|
||||
|
|
|
@ -62,11 +62,10 @@ class DatabaseWriter {
|
|||
..write('class $className extends ')
|
||||
..writeDriftRef('GeneratedDatabase')
|
||||
..writeln('{')
|
||||
..writeln(
|
||||
'$className(${firstLeaf.refDrift('QueryExecutor e')}): super(e);');
|
||||
..writeln('$className(${firstLeaf.drift('QueryExecutor e')}): super(e);');
|
||||
|
||||
if (dbScope.options.generateConnectConstructor) {
|
||||
final conn = firstLeaf.refDrift('DatabaseConnection');
|
||||
final conn = firstLeaf.drift('DatabaseConnection');
|
||||
firstLeaf.write('$className.connect($conn c): super.connect(c); \n');
|
||||
}
|
||||
|
||||
|
@ -220,7 +219,7 @@ class DatabaseWriter {
|
|||
|
||||
if (scope.options.storeDateTimeValuesAsText) {
|
||||
// Override database options to reflect that DateTimes are stored as text.
|
||||
final options = schemaScope.refDrift('DriftDatabaseOptions');
|
||||
final options = schemaScope.drift('DriftDatabaseOptions');
|
||||
|
||||
schemaScope
|
||||
..writeln('@override')
|
||||
|
|
|
@ -664,14 +664,18 @@ class _ExpandedVariableWriter {
|
|||
String constructVar(String dartExpr) {
|
||||
// No longer an array here, we apply a for loop if necessary
|
||||
final type = element.innerColumnType(nullable: false);
|
||||
final buffer = StringBuffer('Variable<$type>(');
|
||||
|
||||
final varType = _emitter.drift('Variable');
|
||||
final buffer = StringBuffer('$varType<$type>(');
|
||||
final capture = element.forCaptured;
|
||||
|
||||
final converter = element.typeConverter;
|
||||
if (converter != null) {
|
||||
// Apply the converter.
|
||||
if (element.nullable && converter.canBeSkippedForNulls) {
|
||||
buffer.write('NullAwareTypeConverter.wrapToSql('
|
||||
final nullAware = _emitter.drift('NullAwareTypeConverter');
|
||||
|
||||
buffer.write('$nullAware.wrapToSql('
|
||||
'${_converter(_emitter, element.typeConverter!)}, $dartExpr)');
|
||||
} else {
|
||||
buffer.write(
|
||||
|
|
|
@ -133,8 +133,11 @@ class DataClassWriter {
|
|||
|
||||
final typeConverter = column.typeConverter;
|
||||
if (typeConverter != null && typeConverter.alsoAppliesToJsonConversion) {
|
||||
final type =
|
||||
var type =
|
||||
_emitter.dartCode(AnnotatedDartCode.type(typeConverter.jsonType!));
|
||||
if (column.nullable) {
|
||||
type = '$type?';
|
||||
}
|
||||
|
||||
final fromConverter = "serializer.fromJson<$type>(json['$jsonKey'])";
|
||||
final converterField = _converter(column);
|
||||
|
|
|
@ -7,8 +7,6 @@ import '../analysis/options.dart';
|
|||
import 'import_manager.dart';
|
||||
import 'queries/sql_writer.dart';
|
||||
|
||||
Uri _driftImport = Uri.parse('package:drift/drift.dart');
|
||||
|
||||
/// Manages a tree structure which we use to generate code.
|
||||
///
|
||||
/// Each leaf in the tree is a [StringBuffer] that contains some code. A
|
||||
|
@ -287,9 +285,7 @@ class TextEmitter extends _Node {
|
|||
return write(refUri(definition, element));
|
||||
}
|
||||
|
||||
void writeDriftRef(String element) => write(refDrift(element));
|
||||
|
||||
String refDrift(String element) => refUri(_driftImport, element);
|
||||
void writeDriftRef(String element) => write(drift(element));
|
||||
|
||||
void writeDart(AnnotatedDartCode code) => write(dartCode(code));
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ dependencies:
|
|||
recase: '>=2.0.1 <5.0.0'
|
||||
meta: ^1.1.0
|
||||
path: ^1.6.0
|
||||
json_annotation: ^4.6.0
|
||||
json_annotation: ^4.7.0
|
||||
stream_transform: '>=0.1.0 <3.0.0'
|
||||
|
||||
# CLI
|
||||
|
@ -42,6 +42,7 @@ dependencies:
|
|||
build_config: '>=0.3.1 <2.0.0'
|
||||
dart_style: '>=1.3.3 <3.0.0'
|
||||
source_gen: '>=0.9.4 <2.0.0'
|
||||
string_scanner: ^1.2.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
|
|
|
@ -86,4 +86,34 @@ sqlite:
|
|||
.withSpan('place_spellfix'),
|
||||
]);
|
||||
});
|
||||
|
||||
group('parses functions', () {
|
||||
test('succesfully', () {
|
||||
final function = KnownSqliteFunction.fromJson('text (int, boolean nUlL)');
|
||||
|
||||
expect(function.returnType.type, BasicType.text);
|
||||
expect(function.argumentTypes, [
|
||||
isA<ResolvedType>().having((e) => e.type, 'type', BasicType.int),
|
||||
isA<ResolvedType>()
|
||||
.having((e) => e.type, 'type', BasicType.int)
|
||||
.having((e) => e.hint, 'hint', const IsBoolean())
|
||||
.having((e) => e.nullable, 'nullable', true),
|
||||
]);
|
||||
});
|
||||
|
||||
test('supports empty args', () {
|
||||
final function = KnownSqliteFunction.fromJson('text ()');
|
||||
|
||||
expect(function.returnType.type, BasicType.text);
|
||||
expect(function.argumentTypes, isEmpty);
|
||||
});
|
||||
|
||||
test('fails for invalid syntax', () {
|
||||
final throws = throwsFormatException;
|
||||
|
||||
expect(() => KnownSqliteFunction.fromJson('x'), throws);
|
||||
expect(() => KnownSqliteFunction.fromJson('(boolean)'), throws);
|
||||
expect(() => KnownSqliteFunction.fromJson('int (boolean, )'), throws);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:drift/drift.dart' show DriftSqlType;
|
||||
import 'package:drift_dev/src/analysis/results/results.dart';
|
||||
import 'package:drift_dev/src/analysis/options.dart';
|
||||
import 'package:sqlparser/sqlparser.dart';
|
||||
import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../../test_utils.dart';
|
||||
|
@ -257,4 +257,36 @@ LEFT JOIN tableB1 AS tableB2 -- nullable
|
|||
[false, true, false, true],
|
||||
);
|
||||
});
|
||||
|
||||
test('supports custom functions', () async {
|
||||
final withoutOptions =
|
||||
TestBackend.inTest({'a|lib/a.drift': 'a: SELECT my_function();'});
|
||||
var result = await withoutOptions.analyze('package:a/a.drift');
|
||||
expect(result.allErrors, [
|
||||
isDriftError('Function my_function could not be found')
|
||||
.withSpan('my_function'),
|
||||
isDriftError(startsWith('Expression has an unknown type'))
|
||||
.withSpan('my_function()'),
|
||||
]);
|
||||
|
||||
final withOptions =
|
||||
TestBackend.inTest({'a|lib/a.drift': 'a: SELECT my_function(?, ?);'},
|
||||
options: DriftOptions.defaults(
|
||||
sqliteAnalysisOptions: SqliteAnalysisOptions(knownFunctions: {
|
||||
'my_function':
|
||||
KnownSqliteFunction.fromJson('boolean (int, text)')
|
||||
}),
|
||||
));
|
||||
result = await withOptions.analyze('package:a/a.drift');
|
||||
|
||||
withOptions.expectNoErrors();
|
||||
|
||||
final query = result.fileAnalysis!.resolvedQueries.values.single;
|
||||
expect(query.resultSet!.columns, [
|
||||
isA<ResultColumn>().having((e) => e.sqlType, 'sqlType', DriftSqlType.bool)
|
||||
]);
|
||||
|
||||
final args = query.variables;
|
||||
expect(args.map((e) => e.sqlType), [DriftSqlType.int, DriftSqlType.string]);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ class Database extends _$Database {}
|
|||
|
||||
checkOutputs({
|
||||
'a|lib/main.drift.dart': decodedMatches(contains(r'''
|
||||
static TypeConverter<Priority, String> $converterpriority =
|
||||
static JsonTypeConverter2<Priority, String, String> $converterpriority =
|
||||
const EnumNameConverter<Priority>(Priority.values);''')),
|
||||
}, writer.dartOutputs, writer);
|
||||
},
|
||||
|
|
|
@ -142,7 +142,7 @@ class $CategoriesTable extends Categories
|
|||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$CategoriesTable(this.attachedDatabase, [this._alias]);
|
||||
final VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id', aliasedName, false,
|
||||
|
@ -151,12 +151,12 @@ class $CategoriesTable extends Categories
|
|||
requiredDuringInsert: false,
|
||||
defaultConstraints:
|
||||
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
||||
final VerificationMeta _nameMeta = const VerificationMeta('name');
|
||||
static const VerificationMeta _nameMeta = const VerificationMeta('name');
|
||||
@override
|
||||
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
||||
'name', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
final VerificationMeta _colorMeta = const VerificationMeta('color');
|
||||
static const VerificationMeta _colorMeta = const VerificationMeta('color');
|
||||
@override
|
||||
late final GeneratedColumnWithTypeConverter<Color, int> color =
|
||||
GeneratedColumn<int>('color', aliasedName, false,
|
||||
|
@ -382,7 +382,7 @@ class $TodoEntriesTable extends TodoEntries
|
|||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$TodoEntriesTable(this.attachedDatabase, [this._alias]);
|
||||
final VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id', aliasedName, false,
|
||||
|
@ -391,13 +391,14 @@ class $TodoEntriesTable extends TodoEntries
|
|||
requiredDuringInsert: false,
|
||||
defaultConstraints:
|
||||
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
||||
final VerificationMeta _descriptionMeta =
|
||||
static const VerificationMeta _descriptionMeta =
|
||||
const VerificationMeta('description');
|
||||
@override
|
||||
late final GeneratedColumn<String> description = GeneratedColumn<String>(
|
||||
'description', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
final VerificationMeta _categoryMeta = const VerificationMeta('category');
|
||||
static const VerificationMeta _categoryMeta =
|
||||
const VerificationMeta('category');
|
||||
@override
|
||||
late final GeneratedColumn<int> category = GeneratedColumn<int>(
|
||||
'category', aliasedName, true,
|
||||
|
@ -405,7 +406,8 @@ class $TodoEntriesTable extends TodoEntries
|
|||
requiredDuringInsert: false,
|
||||
defaultConstraints:
|
||||
GeneratedColumn.constraintIsAlways('REFERENCES categories (id)'));
|
||||
final VerificationMeta _dueDateMeta = const VerificationMeta('dueDate');
|
||||
static const VerificationMeta _dueDateMeta =
|
||||
const VerificationMeta('dueDate');
|
||||
@override
|
||||
late final GeneratedColumn<DateTime> dueDate = GeneratedColumn<DateTime>(
|
||||
'due_date', aliasedName, true,
|
||||
|
@ -564,7 +566,7 @@ class TextEntries extends Table
|
|||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
TextEntries(this.attachedDatabase, [this._alias]);
|
||||
final VerificationMeta _descriptionMeta =
|
||||
static const VerificationMeta _descriptionMeta =
|
||||
const VerificationMeta('description');
|
||||
late final GeneratedColumn<String> description = GeneratedColumn<String>(
|
||||
'description', aliasedName, false,
|
||||
|
@ -594,7 +596,7 @@ class TextEntries extends Table
|
|||
}
|
||||
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => const <GeneratedColumn>{};
|
||||
Set<GeneratedColumn> get $primaryKey => const {};
|
||||
@override
|
||||
TextEntrie map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
|
@ -670,7 +672,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||
}
|
||||
|
||||
@override
|
||||
Iterable<TableInfo<Table, dynamic>> get allTables =>
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [
|
||||
|
|
|
@ -115,7 +115,7 @@ class $NotesTable extends Notes with TableInfo<$NotesTable, Note> {
|
|||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$NotesTable(this.attachedDatabase, [this._alias]);
|
||||
final VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id', aliasedName, false,
|
||||
|
@ -124,7 +124,8 @@ class $NotesTable extends Notes with TableInfo<$NotesTable, Note> {
|
|||
requiredDuringInsert: false,
|
||||
defaultConstraints:
|
||||
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
||||
final VerificationMeta _contentMeta = const VerificationMeta('content');
|
||||
static const VerificationMeta _contentMeta =
|
||||
const VerificationMeta('content');
|
||||
@override
|
||||
late final GeneratedColumn<String> content = GeneratedColumn<String>(
|
||||
'content', aliasedName, false,
|
||||
|
@ -175,7 +176,7 @@ abstract class _$MyEncryptedDatabase extends GeneratedDatabase {
|
|||
_$MyEncryptedDatabase(QueryExecutor e) : super(e);
|
||||
late final $NotesTable notes = $NotesTable(this);
|
||||
@override
|
||||
Iterable<TableInfo<Table, dynamic>> get allTables =>
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [notes];
|
||||
|
|
|
@ -115,13 +115,13 @@ class Entries extends Table with TableInfo<Entries, Entrie> {
|
|||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
Entries(this.attachedDatabase, [this._alias]);
|
||||
final VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id', aliasedName, false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'PRIMARY KEY');
|
||||
final VerificationMeta _valueMeta = const VerificationMeta('value');
|
||||
static const VerificationMeta _valueMeta = const VerificationMeta('value');
|
||||
late final GeneratedColumn<String> value = GeneratedColumn<String>(
|
||||
'text', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
|
@ -201,7 +201,7 @@ abstract class _$MyDatabase extends GeneratedDatabase {
|
|||
}
|
||||
|
||||
@override
|
||||
Iterable<TableInfo<Table, dynamic>> get allTables =>
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [entries];
|
||||
|
|
|
@ -823,7 +823,7 @@ class GroupCount extends ViewInfo<GroupCount, GroupCountData>
|
|||
String get entityName => 'group_count';
|
||||
@override
|
||||
String get createViewStmt =>
|
||||
'CREATE VIEW group_count AS SELECT\n users.*,\n (SELECT COUNT(*) FROM "groups" WHERE owner = users.id) AS group_count\n FROM users;';
|
||||
'CREATE VIEW group_count AS SELECT users.*, (SELECT COUNT(*) FROM "groups" WHERE owner = users.id) AS group_count FROM users';
|
||||
@override
|
||||
GroupCount get asDslTable => this;
|
||||
@override
|
||||
|
|
|
@ -7,6 +7,10 @@ CREATE VIRTUAL TABLE search_in_posts USING fts5 (
|
|||
content_rowid=id
|
||||
);
|
||||
|
||||
search: WITH relevant_ports AS (SELECT rowid FROM search_in_posts WHERE search_in_posts MATCH ?)
|
||||
SELECT posts.* FROM relevant_ports results
|
||||
INNER JOIN posts ON id = results.rowid;
|
||||
|
||||
-- Keep fts5 table and posts synchronized
|
||||
|
||||
CREATE TRIGGER posts_insert AFTER INSERT ON posts BEGIN
|
||||
|
|
|
@ -205,5 +205,20 @@ i0.Trigger get postsDelete => i0.Trigger(
|
|||
|
||||
class SearchDrift extends i2.ModularAccessor {
|
||||
SearchDrift(i0.GeneratedDatabase db) : super(db);
|
||||
i0.Selectable<i3.Post> search(String var1) {
|
||||
return customSelect(
|
||||
'WITH relevant_ports AS (SELECT "rowid" FROM search_in_posts WHERE search_in_posts MATCH ?1) SELECT posts.* FROM relevant_ports AS results INNER JOIN posts ON id = results."rowid"',
|
||||
variables: [
|
||||
i0.Variable<String>(var1)
|
||||
],
|
||||
readsFrom: {
|
||||
searchInPosts,
|
||||
posts,
|
||||
}).asyncMap(posts.mapFromRow);
|
||||
}
|
||||
|
||||
i1.SearchInPosts get searchInPosts =>
|
||||
this.resultSet<i1.SearchInPosts>('search_in_posts');
|
||||
i3.Posts get posts => this.resultSet<i3.Posts>('posts');
|
||||
i3.PostsDrift get postsDrift => this.accessor(i3.PostsDrift.new);
|
||||
}
|
||||
|
|
|
@ -115,13 +115,13 @@ class Entries extends Table with TableInfo<Entries, Entrie> {
|
|||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
Entries(this.attachedDatabase, [this._alias]);
|
||||
final VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id', aliasedName, false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'PRIMARY KEY');
|
||||
final VerificationMeta _valueMeta = const VerificationMeta('value');
|
||||
static const VerificationMeta _valueMeta = const VerificationMeta('value');
|
||||
late final GeneratedColumn<String> value = GeneratedColumn<String>(
|
||||
'text', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
|
@ -202,7 +202,7 @@ abstract class _$MyDatabase extends GeneratedDatabase {
|
|||
}
|
||||
|
||||
@override
|
||||
Iterable<TableInfo<Table, dynamic>> get allTables =>
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [entries];
|
||||
|
|
|
@ -117,12 +117,12 @@ class $KeyValuesTable extends KeyValues
|
|||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$KeyValuesTable(this.attachedDatabase, [this._alias]);
|
||||
final VerificationMeta _keyMeta = const VerificationMeta('key');
|
||||
static const VerificationMeta _keyMeta = const VerificationMeta('key');
|
||||
@override
|
||||
late final GeneratedColumn<String> key = GeneratedColumn<String>(
|
||||
'key', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
final VerificationMeta _valueMeta = const VerificationMeta('value');
|
||||
static const VerificationMeta _valueMeta = const VerificationMeta('value');
|
||||
@override
|
||||
late final GeneratedColumn<String> value = GeneratedColumn<String>(
|
||||
'value', aliasedName, false,
|
||||
|
@ -176,7 +176,7 @@ abstract class _$Database extends GeneratedDatabase {
|
|||
_$Database(QueryExecutor e) : super(e);
|
||||
late final $KeyValuesTable keyValues = $KeyValuesTable(this);
|
||||
@override
|
||||
Iterable<TableInfo<Table, dynamic>> get allTables =>
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [keyValues];
|
||||
|
|
|
@ -210,7 +210,7 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
|
|||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$UsersTable(this.attachedDatabase, [this._alias]);
|
||||
final VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id', aliasedName, false,
|
||||
|
@ -219,23 +219,24 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
|
|||
requiredDuringInsert: false,
|
||||
defaultConstraints:
|
||||
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
||||
final VerificationMeta _nameMeta = const VerificationMeta('name');
|
||||
static const VerificationMeta _nameMeta = const VerificationMeta('name');
|
||||
@override
|
||||
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
||||
'name', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
final VerificationMeta _birthDateMeta = const VerificationMeta('birthDate');
|
||||
static const VerificationMeta _birthDateMeta =
|
||||
const VerificationMeta('birthDate');
|
||||
@override
|
||||
late final GeneratedColumn<DateTime> birthDate = GeneratedColumn<DateTime>(
|
||||
'birth_date', aliasedName, false,
|
||||
type: DriftSqlType.dateTime, requiredDuringInsert: true);
|
||||
final VerificationMeta _profilePictureMeta =
|
||||
static const VerificationMeta _profilePictureMeta =
|
||||
const VerificationMeta('profilePicture');
|
||||
@override
|
||||
late final GeneratedColumn<Uint8List> profilePicture =
|
||||
GeneratedColumn<Uint8List>('profile_picture', aliasedName, true,
|
||||
type: DriftSqlType.blob, requiredDuringInsert: false);
|
||||
final VerificationMeta _preferencesMeta =
|
||||
static const VerificationMeta _preferencesMeta =
|
||||
const VerificationMeta('preferences');
|
||||
@override
|
||||
late final GeneratedColumnWithTypeConverter<Preferences?, String>
|
||||
|
@ -455,17 +456,19 @@ class $FriendshipsTable extends Friendships
|
|||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$FriendshipsTable(this.attachedDatabase, [this._alias]);
|
||||
final VerificationMeta _firstUserMeta = const VerificationMeta('firstUser');
|
||||
static const VerificationMeta _firstUserMeta =
|
||||
const VerificationMeta('firstUser');
|
||||
@override
|
||||
late final GeneratedColumn<int> firstUser = GeneratedColumn<int>(
|
||||
'first_user', aliasedName, false,
|
||||
type: DriftSqlType.int, requiredDuringInsert: true);
|
||||
final VerificationMeta _secondUserMeta = const VerificationMeta('secondUser');
|
||||
static const VerificationMeta _secondUserMeta =
|
||||
const VerificationMeta('secondUser');
|
||||
@override
|
||||
late final GeneratedColumn<int> secondUser = GeneratedColumn<int>(
|
||||
'second_user', aliasedName, false,
|
||||
type: DriftSqlType.int, requiredDuringInsert: true);
|
||||
final VerificationMeta _reallyGoodFriendsMeta =
|
||||
static const VerificationMeta _reallyGoodFriendsMeta =
|
||||
const VerificationMeta('reallyGoodFriends');
|
||||
@override
|
||||
late final GeneratedColumn<bool> reallyGoodFriends =
|
||||
|
@ -625,7 +628,7 @@ abstract class _$Database extends GeneratedDatabase {
|
|||
}
|
||||
|
||||
@override
|
||||
Iterable<TableInfo<Table, dynamic>> get allTables =>
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [users, friendships];
|
||||
|
|
|
@ -97,7 +97,7 @@ class $FoosTable extends Foos with TableInfo<$FoosTable, Foo> {
|
|||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$FoosTable(this.attachedDatabase, [this._alias]);
|
||||
final VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id', aliasedName, false,
|
||||
|
@ -234,7 +234,7 @@ class $BarsTable extends Bars with TableInfo<$BarsTable, Bar> {
|
|||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$BarsTable(this.attachedDatabase, [this._alias]);
|
||||
final VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id', aliasedName, false,
|
||||
|
@ -282,7 +282,7 @@ abstract class _$_FakeDb extends GeneratedDatabase {
|
|||
late final $FoosTable foos = $FoosTable(this);
|
||||
late final $BarsTable bars = $BarsTable(this);
|
||||
@override
|
||||
Iterable<TableInfo<Table, dynamic>> get allTables =>
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [foos, bars];
|
||||
|
|
|
@ -601,7 +601,7 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
session.context.reportError(AnalysisError(
|
||||
type: AnalysisErrorType.unknownFunction,
|
||||
message: 'Function ${e.name} could not be found',
|
||||
relevantNode: e,
|
||||
relevantNode: e.nameToken ?? e,
|
||||
));
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -217,6 +217,9 @@ class TableValuedFunction extends Queryable
|
|||
@override
|
||||
final String name;
|
||||
|
||||
@override
|
||||
Token? nameToken;
|
||||
|
||||
@override
|
||||
final String? as;
|
||||
|
||||
|
|
|
@ -7,6 +7,9 @@ class AggregateFunctionInvocation extends Expression
|
|||
@override
|
||||
String get name => function.identifier;
|
||||
|
||||
@override
|
||||
Token? nameToken;
|
||||
|
||||
@override
|
||||
FunctionParameters parameters;
|
||||
Expression? filter;
|
||||
|
@ -18,7 +21,7 @@ class AggregateFunctionInvocation extends Expression
|
|||
required this.function,
|
||||
required this.parameters,
|
||||
this.filter,
|
||||
});
|
||||
}) : nameToken = function;
|
||||
|
||||
@override
|
||||
R accept<A, R>(AstVisitor<A, R> visitor, A arg) {
|
||||
|
|
|
@ -10,6 +10,8 @@ abstract class SqlInvocation implements AstNode {
|
|||
/// The name of the function being called
|
||||
String get name;
|
||||
|
||||
Token? nameToken;
|
||||
|
||||
FunctionParameters get parameters;
|
||||
}
|
||||
|
||||
|
@ -24,6 +26,9 @@ class FunctionExpression extends Expression
|
|||
@override
|
||||
FunctionParameters parameters;
|
||||
|
||||
@override
|
||||
Token? nameToken;
|
||||
|
||||
FunctionExpression({required this.name, required this.parameters});
|
||||
|
||||
@override
|
||||
|
|
|
@ -33,9 +33,6 @@ class Parser {
|
|||
final List<ParsingError> errors = [];
|
||||
final AutoCompleteEngine? autoComplete;
|
||||
|
||||
// todo remove this and don't be that lazy in driftFile()
|
||||
var _lastStmtHadParsingError = false;
|
||||
|
||||
/// Whether to enable the extensions drift makes to the sql grammar.
|
||||
final bool enableDriftExtensions;
|
||||
|
||||
|
@ -242,31 +239,8 @@ class Parser {
|
|||
final first = _peek;
|
||||
final foundComponents = <PartOfDriftFile?>[];
|
||||
|
||||
// (we try again if the last statement had a parsing error)
|
||||
|
||||
// first, parse import statements
|
||||
for (var stmt = _parseAsStatement(_import);
|
||||
stmt != null || _lastStmtHadParsingError;
|
||||
stmt = _parseAsStatement(_import)) {
|
||||
foundComponents.add(stmt);
|
||||
}
|
||||
|
||||
// next, table declarations
|
||||
for (var stmt = _parseAsStatement(_create);
|
||||
stmt != null || _lastStmtHadParsingError;
|
||||
stmt = _parseAsStatement(_create)) {
|
||||
foundComponents.add(stmt);
|
||||
}
|
||||
|
||||
// finally, declared statements
|
||||
for (var stmt = _parseAsStatement(_declaredStatement);
|
||||
stmt != null || _lastStmtHadParsingError;
|
||||
stmt = _parseAsStatement(_declaredStatement)) {
|
||||
foundComponents.add(stmt);
|
||||
}
|
||||
|
||||
if (!_isAtEnd) {
|
||||
_error('Expected the file to end here.');
|
||||
while (!_isAtEnd) {
|
||||
foundComponents.add(_parseAsStatement(_partOfDriftFile));
|
||||
}
|
||||
|
||||
foundComponents.removeWhere((c) => c == null);
|
||||
|
@ -275,11 +249,26 @@ class Parser {
|
|||
if (foundComponents.isNotEmpty) {
|
||||
file.setSpan(first, _previous);
|
||||
} else {
|
||||
_suggestHintForTokens([TokenType.create, TokenType.import]);
|
||||
|
||||
if (_reportAutoComplete) {}
|
||||
|
||||
file.setSpan(first, first); // empty file
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
PartOfDriftFile _partOfDriftFile() {
|
||||
final found = _import() ?? _create() ?? _declaredStatement();
|
||||
|
||||
if (found != null) {
|
||||
return found;
|
||||
}
|
||||
|
||||
_error('Expected `IMPORT`, `CREATE`, or an identifier starting a compiled '
|
||||
'query.');
|
||||
}
|
||||
|
||||
ImportStatement? _import() {
|
||||
if (_matchOne(TokenType.import)) {
|
||||
final importToken = _previous;
|
||||
|
@ -393,7 +382,6 @@ class Parser {
|
|||
/// semicolon if one exists.
|
||||
T? _parseAsStatement<T extends Statement>(T? Function() parser,
|
||||
{bool requireSemicolon = true}) {
|
||||
_lastStmtHadParsingError = false;
|
||||
final first = _peek;
|
||||
T? result;
|
||||
try {
|
||||
|
@ -405,7 +393,6 @@ class Parser {
|
|||
result.setSpan(first, _previous);
|
||||
}
|
||||
} on ParsingError {
|
||||
_lastStmtHadParsingError = true;
|
||||
// the error is added to the list errors, so ignore. We skip after the
|
||||
// next semicolon to parse the next statement.
|
||||
_synchronize(TokenType.semicolon, skipTarget: true);
|
||||
|
@ -422,13 +409,15 @@ class Parser {
|
|||
return result;
|
||||
}
|
||||
|
||||
List<CrudStatement> _crudStatements() {
|
||||
List<CrudStatement> _crudStatements(bool Function() reachedEnd) {
|
||||
final stmts = <CrudStatement>[];
|
||||
|
||||
for (var stmt = _parseAsStatement(_crud);
|
||||
stmt != null || _lastStmtHadParsingError;
|
||||
stmt = _parseAsStatement(_crud)) {
|
||||
if (stmt != null) stmts.add(stmt);
|
||||
while (!reachedEnd()) {
|
||||
final stmt = _parseAsStatement(_crud);
|
||||
|
||||
if (stmt != null) {
|
||||
stmts.add(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
return stmts;
|
||||
|
@ -437,7 +426,7 @@ class Parser {
|
|||
/// Parses a block, which consists of statements between `BEGIN` and `END`.
|
||||
Block _consumeBlock() {
|
||||
final begin = _consume(TokenType.begin, 'Expected BEGIN');
|
||||
final stmts = _crudStatements();
|
||||
final stmts = _crudStatements(() => _check(TokenType.end));
|
||||
final end = _consume(TokenType.end, 'Expected END');
|
||||
|
||||
return Block(stmts)
|
||||
|
@ -449,7 +438,8 @@ class Parser {
|
|||
TransactionBlock _transactionBlock() {
|
||||
final first = _peek;
|
||||
final begin = _beginStatement();
|
||||
final stmts = _crudStatements();
|
||||
final stmts = _crudStatements(
|
||||
() => _checkAny(const [TokenType.commit, TokenType.end]));
|
||||
final end = _commit();
|
||||
|
||||
return TransactionBlock(begin: begin, innerStatements: stmts, commit: end)
|
||||
|
@ -889,6 +879,7 @@ class Parser {
|
|||
}
|
||||
|
||||
return FunctionExpression(name: first.identifier, parameters: parameters)
|
||||
..nameToken = first
|
||||
..setSpan(first, rightParen);
|
||||
} else {
|
||||
// Ok, just a regular reference then
|
||||
|
|
|
@ -219,4 +219,21 @@ SELECT DISTINCT A.* FROM works A, works B ON A.id =
|
|||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('allows statements to appear in any order', () {
|
||||
final result =
|
||||
SqlEngine(EngineOptions(useDriftExtensions: true)).parseDriftFile('''
|
||||
CREATE TABLE foo (
|
||||
a INTEGER NOT NULL
|
||||
);
|
||||
|
||||
import 'b.dart';
|
||||
|
||||
a: SELECT * FROM foo;
|
||||
|
||||
CREATE INDEX x ON foo (a);
|
||||
''');
|
||||
|
||||
expect(result.errors, isEmpty);
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue