mirror of https://github.com/AMT-Cheif/drift.git
433 lines
13 KiB
Dart
433 lines
13 KiB
Dart
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:recase/recase.dart';
|
|
import 'package:sqlparser/sqlparser.dart'
|
|
show BasicType, ResolvedType, SchemaFromCreateTable, SqliteVersion;
|
|
import 'package:string_scanner/string_scanner.dart';
|
|
|
|
part '../generated/analysis/options.g.dart';
|
|
|
|
/// Controllable options to define the behavior of the analyzer and the
|
|
/// generator.
|
|
@JsonSerializable()
|
|
class DriftOptions {
|
|
static const _defaultSqliteVersion = SqliteVersion.v3(34);
|
|
|
|
/// Whether moor should generate a `fromJsonString` factory for data classes.
|
|
/// It basically wraps the regular `fromJson` constructor in a `json.decode`
|
|
/// call.
|
|
@JsonKey(name: 'write_from_json_string_constructor', defaultValue: false)
|
|
final bool generateFromJsonStringConstructor;
|
|
|
|
/// Overrides [Object.hashCode], [Object.==] and [Object.toString] in classes
|
|
/// generated for custom queries.
|
|
///
|
|
/// The `toString` override was added in a later version, we kept the original
|
|
/// name for backwards compatibility.
|
|
@JsonKey(name: 'override_hash_and_equals_in_result_sets', defaultValue: false)
|
|
final bool overrideHashAndEqualsInResultSets;
|
|
|
|
/// Remove verification logic in the generated code.
|
|
@JsonKey(name: 'skip_verification_code', defaultValue: false)
|
|
final bool skipVerificationCode;
|
|
|
|
/// Use a `<data-class>Companion` pattern instead of `<table-class>Companion`
|
|
/// when naming companions.
|
|
@JsonKey(name: 'use_data_class_name_for_companions', defaultValue: false)
|
|
final bool useDataClassNameForCompanions;
|
|
|
|
/// For a column defined in a moor file, use the name directly instead of
|
|
/// the transformed `camelCaseDartGetter`.
|
|
@JsonKey(
|
|
name: 'use_column_name_as_json_key_when_defined_in_moor_file',
|
|
defaultValue: true)
|
|
final bool useColumnNameAsJsonKeyWhenDefinedInMoorFile;
|
|
|
|
/// Generate a `connect` constructor in database superclasses. This is
|
|
/// required to run databases in a background isolate.
|
|
@JsonKey(name: 'generate_connect_constructor', defaultValue: false)
|
|
final bool generateConnectConstructor;
|
|
|
|
@JsonKey(name: 'sqlite_modules', defaultValue: [])
|
|
@Deprecated('Use effectiveModules instead')
|
|
final List<SqlModule> modules;
|
|
|
|
@JsonKey(name: 'sqlite')
|
|
final SqliteAnalysisOptions? sqliteAnalysisOptions;
|
|
|
|
@JsonKey(name: 'sql')
|
|
final DialectOptions? dialect;
|
|
|
|
@JsonKey(name: 'data_class_to_companions', defaultValue: true)
|
|
final bool dataClassToCompanions;
|
|
|
|
@JsonKey(name: 'mutable_classes', defaultValue: false)
|
|
final bool generateMutableClasses;
|
|
|
|
/// Whether generated query classes should inherit from the `CustomResultSet`
|
|
/// and expose their underlying raw `row`.
|
|
@JsonKey(name: 'raw_result_set_data', defaultValue: false)
|
|
final bool rawResultSetData;
|
|
|
|
@JsonKey(name: 'apply_converters_on_variables', defaultValue: true)
|
|
final bool applyConvertersOnVariables;
|
|
|
|
@JsonKey(name: 'generate_values_in_copy_with', defaultValue: true)
|
|
final bool generateValuesInCopyWith;
|
|
|
|
@JsonKey(name: 'named_parameters', defaultValue: false)
|
|
final bool generateNamedParameters;
|
|
|
|
@JsonKey(name: 'named_parameters_always_required', defaultValue: false)
|
|
final bool namedParametersAlwaysRequired;
|
|
|
|
@JsonKey(name: 'scoped_dart_components', defaultValue: true)
|
|
final bool scopedDartComponents;
|
|
|
|
/// Whether `DateTime` columns should be stored as text (via
|
|
/// [DateTime.toIso8601String]) instead of integers (unix timestamp).
|
|
@JsonKey(defaultValue: false)
|
|
final bool storeDateTimeValuesAsText;
|
|
|
|
@JsonKey(name: 'case_from_dart_to_sql', defaultValue: CaseFromDartToSql.snake)
|
|
final CaseFromDartToSql caseFromDartToSql;
|
|
|
|
@internal
|
|
const DriftOptions.defaults({
|
|
this.generateFromJsonStringConstructor = false,
|
|
this.overrideHashAndEqualsInResultSets = false,
|
|
this.skipVerificationCode = false,
|
|
this.useDataClassNameForCompanions = false,
|
|
this.useColumnNameAsJsonKeyWhenDefinedInMoorFile = true,
|
|
this.generateConnectConstructor = false,
|
|
this.dataClassToCompanions = true,
|
|
this.generateMutableClasses = false,
|
|
this.rawResultSetData = false,
|
|
this.applyConvertersOnVariables = true,
|
|
this.generateValuesInCopyWith = true,
|
|
this.generateNamedParameters = false,
|
|
this.namedParametersAlwaysRequired = false,
|
|
this.scopedDartComponents = true,
|
|
this.modules = const [],
|
|
this.sqliteAnalysisOptions,
|
|
this.storeDateTimeValuesAsText = false,
|
|
this.dialect = const DialectOptions(SqlDialect.sqlite, null),
|
|
this.caseFromDartToSql = CaseFromDartToSql.snake,
|
|
});
|
|
|
|
DriftOptions({
|
|
required this.generateFromJsonStringConstructor,
|
|
required this.overrideHashAndEqualsInResultSets,
|
|
required this.skipVerificationCode,
|
|
required this.useDataClassNameForCompanions,
|
|
required this.useColumnNameAsJsonKeyWhenDefinedInMoorFile,
|
|
required this.generateConnectConstructor,
|
|
required this.dataClassToCompanions,
|
|
required this.generateMutableClasses,
|
|
required this.rawResultSetData,
|
|
required this.applyConvertersOnVariables,
|
|
required this.generateValuesInCopyWith,
|
|
required this.generateNamedParameters,
|
|
required this.namedParametersAlwaysRequired,
|
|
required this.scopedDartComponents,
|
|
required this.modules,
|
|
required this.sqliteAnalysisOptions,
|
|
required this.storeDateTimeValuesAsText,
|
|
required this.caseFromDartToSql,
|
|
this.dialect,
|
|
}) {
|
|
// ignore: deprecated_member_use_from_same_package
|
|
if (sqliteAnalysisOptions != null && modules.isNotEmpty) {
|
|
throw ArgumentError.value(
|
|
// ignore: deprecated_member_use_from_same_package
|
|
modules,
|
|
'modules',
|
|
'May not be set when sqlite options are present. \n'
|
|
'Try moving modules into the sqlite block.',
|
|
);
|
|
}
|
|
|
|
if (dialect != null && sqliteAnalysisOptions != null) {
|
|
throw ArgumentError.value(
|
|
sqliteAnalysisOptions,
|
|
'sqlite',
|
|
'The sqlite field cannot be used together the `sql` option. '
|
|
'Try moving it to `sql.options`.',
|
|
);
|
|
}
|
|
}
|
|
|
|
factory DriftOptions.fromJson(Map json) => _$DriftOptionsFromJson(json);
|
|
|
|
SqliteAnalysisOptions? get sqliteOptions {
|
|
return dialect?.options ?? sqliteAnalysisOptions;
|
|
}
|
|
|
|
/// All enabled sqlite modules from these options.
|
|
List<SqlModule> get effectiveModules {
|
|
// ignore: deprecated_member_use_from_same_package
|
|
return sqliteOptions?.modules ?? modules;
|
|
}
|
|
|
|
/// Whether the [module] has been enabled in this configuration.
|
|
bool hasModule(SqlModule module) => effectiveModules.contains(module);
|
|
|
|
/// Checks whether a deprecated option is enabled.
|
|
///
|
|
/// At this time, all deprecated options have been removed, meaning that this
|
|
/// getter always returns `false`.
|
|
bool get enabledDeprecatedOption => false;
|
|
|
|
SqlDialect get effectiveDialect => dialect?.dialect ?? SqlDialect.sqlite;
|
|
|
|
/// The assumed sqlite version used when analyzing queries.
|
|
SqliteVersion get sqliteVersion {
|
|
return sqliteOptions?.version ?? _defaultSqliteVersion;
|
|
}
|
|
|
|
Map<String, Object?> toJson() => _$DriftOptionsToJson(this);
|
|
}
|
|
|
|
@JsonSerializable()
|
|
class DialectOptions {
|
|
final SqlDialect dialect;
|
|
final SqliteAnalysisOptions? options;
|
|
|
|
const DialectOptions(this.dialect, this.options);
|
|
|
|
factory DialectOptions.fromJson(Map json) => _$DialectOptionsFromJson(json);
|
|
|
|
Map<String, Object?> toJson() => _$DialectOptionsToJson(this);
|
|
}
|
|
|
|
@JsonSerializable()
|
|
class SqliteAnalysisOptions {
|
|
@JsonKey(name: 'modules')
|
|
final List<SqlModule> modules;
|
|
|
|
@_SqliteVersionConverter()
|
|
final SqliteVersion? version;
|
|
|
|
final Map<String, KnownSqliteFunction> knownFunctions;
|
|
|
|
const SqliteAnalysisOptions({
|
|
this.modules = const [],
|
|
this.version,
|
|
this.knownFunctions = const {},
|
|
});
|
|
|
|
factory SqliteAnalysisOptions.fromJson(Map json) {
|
|
return _$SqliteAnalysisOptionsFromJson(json);
|
|
}
|
|
|
|
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+)');
|
|
|
|
const _SqliteVersionConverter();
|
|
|
|
@override
|
|
SqliteVersion fromJson(String json) {
|
|
final match = _versionRegex.firstMatch(json);
|
|
if (match == null) {
|
|
throw ArgumentError.value(
|
|
json,
|
|
'json',
|
|
'Not a valid sqlite version: Expected format major.minor (e.g. 3.34)',
|
|
);
|
|
}
|
|
|
|
final major = int.parse(match.group(1)!);
|
|
final minor = int.parse(match.group(2)!);
|
|
|
|
final version = SqliteVersion(major, minor, 0);
|
|
if (version < SqliteVersion.minimum) {
|
|
throw ArgumentError.value(
|
|
json,
|
|
'json',
|
|
'Version is not supported for analysis (minimum is '
|
|
'${SqliteVersion.minimum}).',
|
|
);
|
|
} else if (version > SqliteVersion.current) {
|
|
throw ArgumentError.value(
|
|
json,
|
|
'json',
|
|
'Version is not supported for analysis (current maximum is '
|
|
'${SqliteVersion.current}).',
|
|
);
|
|
}
|
|
|
|
return version;
|
|
}
|
|
|
|
@override
|
|
String toJson(SqliteVersion object) {
|
|
return '${object.major}.${object.minor}';
|
|
}
|
|
}
|
|
|
|
/// Set of sqlite modules that require special knowledge from the generator.
|
|
enum SqlModule {
|
|
/// Enables support for the json1 module and its functions when parsing sql
|
|
/// queries.
|
|
json1,
|
|
|
|
/// Enables support for the fts5 module and its functions when parsing sql
|
|
/// queries.
|
|
fts5,
|
|
|
|
/// Enables support for mathematical functions only available in `moor_ffi`.
|
|
// note: We're ignoring the warning because we can't change the json key
|
|
// ignore: constant_identifier_names
|
|
moor_ffi,
|
|
|
|
/// Enables support for [built in math functions][math funs] when analysing
|
|
/// sql queries.
|
|
///
|
|
/// [math funs]: https://www.sqlite.org/lang_mathfunc.html
|
|
math,
|
|
|
|
/// Enables support for the rtree module and its functions when parsing sql
|
|
/// queries.
|
|
rtree,
|
|
|
|
spellfix1,
|
|
}
|
|
|
|
/// The possible values for the case of the table and column names.
|
|
enum CaseFromDartToSql {
|
|
/// Preserves the case of the name as it is in the dart code.
|
|
///
|
|
/// `myColumn` -> `myColumn`.
|
|
preserve,
|
|
|
|
/// Use camelCase.
|
|
///
|
|
/// `my_column` -> `myColumn`.
|
|
@JsonValue('camelCase')
|
|
camel,
|
|
|
|
/// Use CONSTANT_CASE.
|
|
///
|
|
/// `myColumn` -> `MY_COLUMN`.
|
|
@JsonValue('CONSTANT_CASE')
|
|
constant,
|
|
|
|
/// Use snake_case.
|
|
///
|
|
/// `myColumn` -> `my_column`.
|
|
@JsonValue('snake_case')
|
|
snake,
|
|
|
|
/// Use PascalCase.
|
|
///
|
|
/// `my_column` -> `MyColumn`.
|
|
// ignore: constant_identifier_names
|
|
@JsonValue('PascalCase')
|
|
pascal,
|
|
|
|
/// Use lowercase.
|
|
///
|
|
/// `myColumn` -> `mycolumn`.
|
|
@JsonValue('lowercase')
|
|
lower,
|
|
|
|
/// Use UPPERCASE.
|
|
///
|
|
/// `myColumn` -> `MYCOLUMN`.
|
|
@JsonValue('UPPERCASE')
|
|
upper;
|
|
|
|
/// Applies the correct case to the given [name].
|
|
String apply(String name) {
|
|
final reCase = ReCase(name);
|
|
switch (this) {
|
|
case CaseFromDartToSql.preserve:
|
|
return name;
|
|
case CaseFromDartToSql.camel:
|
|
return reCase.camelCase;
|
|
case CaseFromDartToSql.constant:
|
|
return reCase.constantCase;
|
|
case CaseFromDartToSql.snake:
|
|
return reCase.snakeCase;
|
|
case CaseFromDartToSql.pascal:
|
|
return reCase.pascalCase;
|
|
case CaseFromDartToSql.lower:
|
|
return name.toLowerCase();
|
|
case CaseFromDartToSql.upper:
|
|
return name.toUpperCase();
|
|
}
|
|
}
|
|
}
|