mirror of https://github.com/AMT-Cheif/drift.git
Resolve json functions when option is set (#235)
This commit is contained in:
parent
8320ddc788
commit
a0ce3421c9
|
@ -6,4 +6,6 @@ targets:
|
|||
override_hash_and_equals_in_result_sets: true
|
||||
use_column_name_as_json_key_when_defined_in_moor_file: true
|
||||
generate_connect_constructor: true
|
||||
write_from_json_string_constructor: true
|
||||
write_from_json_string_constructor: true
|
||||
sqlite_modules:
|
||||
- json1
|
|
@ -950,6 +950,13 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
|||
readsFrom: {config}).map(_rowToConfig);
|
||||
}
|
||||
|
||||
Selectable<Config> findValidJsons() {
|
||||
return customSelectQuery(
|
||||
'SELECT * FROM config WHERE json_valid(config_value)',
|
||||
variables: [],
|
||||
readsFrom: {config}).map(_rowToConfig);
|
||||
}
|
||||
|
||||
ReadRowIdResult _rowToReadRowIdResult(QueryRow row) {
|
||||
return ReadRowIdResult(
|
||||
rowid: row.readInt('rowid'),
|
||||
|
|
|
@ -30,6 +30,7 @@ CREATE TABLE mytable (
|
|||
readConfig: SELECT * FROM config WHERE config_key = ?;
|
||||
readMultiple: SELECT * FROM config WHERE config_key IN ? ORDER BY $clause;
|
||||
readDynamic: SELECT * FROM config WHERE $predicate;
|
||||
findValidJsons: SELECT * FROM config WHERE json_valid(config_value);
|
||||
|
||||
readRowId: SELECT oid, * FROM config WHERE _rowid_ = $expr;
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ class MoorParser {
|
|||
MoorParser(this.step);
|
||||
|
||||
Future<ParsedMoorFile> parseAndAnalyze() {
|
||||
final result =
|
||||
SqlEngine(useMoorExtensions: true).parseMoorFile(step.content);
|
||||
final engine = step.task.session.spawnEngine();
|
||||
final result = engine.parseMoorFile(step.content);
|
||||
final parsedFile = result.rootNode as MoorFile;
|
||||
|
||||
final createdReaders = <CreateTableReader>[];
|
||||
|
|
|
@ -18,43 +18,48 @@ class MoorOptions {
|
|||
/// 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')
|
||||
@JsonKey(name: 'write_from_json_string_constructor', defaultValue: false)
|
||||
final bool generateFromJsonStringConstructor;
|
||||
|
||||
/// Overrides [Object.hashCode] and [Object.==] in classes generated for
|
||||
/// custom queries.
|
||||
@JsonKey(name: 'override_hash_and_equals_in_result_sets')
|
||||
@JsonKey(name: 'override_hash_and_equals_in_result_sets', defaultValue: false)
|
||||
final bool overrideHashAndEqualsInResultSets;
|
||||
|
||||
/// Also enable the compact query methods from moor files on queries defined
|
||||
/// in a `UseMoor` annotation. Compact queries return a `Selectable` instead
|
||||
/// of generating two methods (with one returning a stream and another
|
||||
/// returning a future)
|
||||
@JsonKey(name: 'compact_query_methods')
|
||||
@JsonKey(name: 'compact_query_methods', defaultValue: false)
|
||||
final bool compactQueryMethods;
|
||||
|
||||
/// Remove verification logic in the generated code.
|
||||
@JsonKey(name: 'skip_verification_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')
|
||||
@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')
|
||||
@JsonKey(
|
||||
name: 'use_column_name_as_json_key_when_defined_in_moor_file',
|
||||
defaultValue: false)
|
||||
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')
|
||||
@JsonKey(name: 'generate_connect_constructor', defaultValue: false)
|
||||
final bool generateConnectConstructor;
|
||||
|
||||
@JsonKey(name: 'sqlite_modules', defaultValue: [])
|
||||
final List<SqlModule> modules;
|
||||
|
||||
/// Whether the [module] has been enabled in this configuration.
|
||||
bool hasModule(SqlModule module) => modules.contains(module);
|
||||
|
||||
const MoorOptions(
|
||||
{this.generateFromJsonStringConstructor = false,
|
||||
this.overrideHashAndEqualsInResultSets = false,
|
||||
|
|
|
@ -20,21 +20,28 @@ MoorOptions _$MoorOptionsFromJson(Map<String, dynamic> json) {
|
|||
]);
|
||||
final val = MoorOptions(
|
||||
generateFromJsonStringConstructor: $checkedConvert(
|
||||
json, 'write_from_json_string_constructor', (v) => v as bool),
|
||||
overrideHashAndEqualsInResultSets: $checkedConvert(
|
||||
json, 'override_hash_and_equals_in_result_sets', (v) => v as bool),
|
||||
json, 'write_from_json_string_constructor', (v) => v as bool) ??
|
||||
false,
|
||||
overrideHashAndEqualsInResultSets: $checkedConvert(json,
|
||||
'override_hash_and_equals_in_result_sets', (v) => v as bool) ??
|
||||
false,
|
||||
compactQueryMethods:
|
||||
$checkedConvert(json, 'compact_query_methods', (v) => v as bool),
|
||||
$checkedConvert(json, 'compact_query_methods', (v) => v as bool) ??
|
||||
false,
|
||||
skipVerificationCode:
|
||||
$checkedConvert(json, 'skip_verification_code', (v) => v as bool),
|
||||
$checkedConvert(json, 'skip_verification_code', (v) => v as bool) ??
|
||||
false,
|
||||
useDataClassNameForCompanions: $checkedConvert(
|
||||
json, 'use_data_class_name_for_companions', (v) => v as bool),
|
||||
json, 'use_data_class_name_for_companions', (v) => v as bool) ??
|
||||
false,
|
||||
useColumnNameAsJsonKeyWhenDefinedInMoorFile: $checkedConvert(
|
||||
json,
|
||||
'use_column_name_as_json_key_when_defined_in_moor_file',
|
||||
(v) => v as bool),
|
||||
json,
|
||||
'use_column_name_as_json_key_when_defined_in_moor_file',
|
||||
(v) => v as bool) ??
|
||||
false,
|
||||
generateConnectConstructor: $checkedConvert(
|
||||
json, 'generate_connect_constructor', (v) => v as bool),
|
||||
json, 'generate_connect_constructor', (v) => v as bool) ??
|
||||
false,
|
||||
modules: $checkedConvert(
|
||||
json,
|
||||
'sqlite_modules',
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:moor_generator/src/analyzer/runner/file_graph.dart';
|
|||
import 'package:moor_generator/src/analyzer/runner/task.dart';
|
||||
import 'package:moor_generator/src/backends/backend.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:sqlparser/sqlparser.dart';
|
||||
|
||||
import 'options.dart';
|
||||
|
||||
|
@ -32,6 +33,14 @@ class MoorSession {
|
|||
/// which assumes immutable files during a build run).
|
||||
Stream<List<FoundFile>> get changedFiles => _changedFiles.stream;
|
||||
|
||||
/// Creates a properly configured [SqlEngine].
|
||||
SqlEngine spawnEngine() {
|
||||
return SqlEngine(
|
||||
useMoorExtensions: true,
|
||||
enableJson1Module: options.hasModule(SqlModule.json1),
|
||||
);
|
||||
}
|
||||
|
||||
FileType _findFileType(String path) {
|
||||
final extension = p.extension(path);
|
||||
|
||||
|
|
|
@ -18,10 +18,11 @@ class SqlParser {
|
|||
|
||||
final List<SqlQuery> foundQueries = [];
|
||||
|
||||
SqlParser(this.step, this.tables, this.definedQueries);
|
||||
SqlParser(this.step, this.tables, this.definedQueries) {
|
||||
_engine = step.task.session.spawnEngine();
|
||||
}
|
||||
|
||||
void _spawnEngine() {
|
||||
_engine = SqlEngine();
|
||||
tables.map(_mapper.extractStructure).forEach(_engine.registerTable);
|
||||
}
|
||||
|
||||
|
@ -34,20 +35,20 @@ class SqlParser {
|
|||
|
||||
AnalysisContext context;
|
||||
|
||||
if (query is DeclaredDartQuery) {
|
||||
final sql = query.sql;
|
||||
|
||||
try {
|
||||
try {
|
||||
if (query is DeclaredDartQuery) {
|
||||
final sql = query.sql;
|
||||
context = _engine.analyze(sql);
|
||||
} catch (e, s) {
|
||||
step.reportError(MoorError(
|
||||
severity: Severity.criticalError,
|
||||
message: 'Error while trying to parse $name: $e, $s'));
|
||||
return;
|
||||
} else if (query is DeclaredMoorQuery) {
|
||||
context =
|
||||
_engine.analyzeNode(query.query, query.file.parseResult.sql);
|
||||
declaredInMoor = true;
|
||||
}
|
||||
} else if (query is DeclaredMoorQuery) {
|
||||
context = _engine.analyzeNode(query.query, query.file.parseResult.sql);
|
||||
declaredInMoor = true;
|
||||
} catch (e, s) {
|
||||
step.reportError(MoorError(
|
||||
severity: Severity.criticalError,
|
||||
message: 'Error while trying to parse $name: $e, $s'));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var error in context.errors) {
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
## unreleased
|
||||
- Optionally support the `json1` module
|
||||
|
||||
## 0.4.0
|
||||
- Support common table expressions
|
||||
- Handle special `rowid`, `oid`, `__rowid__` references
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:math' show min, max;
|
|||
import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
import 'package:sqlparser/sqlparser.dart';
|
||||
import 'package:sqlparser/src/engine/options.dart';
|
||||
import 'package:sqlparser/src/reader/tokenizer/token.dart';
|
||||
import 'package:sqlparser/src/utils/meta.dart';
|
||||
|
||||
|
|
|
@ -16,10 +16,11 @@ class AnalysisContext {
|
|||
/// A resolver that can be used to obtain the type of a [Typeable]. This
|
||||
/// mostly applies to [Expression]s, [Reference]s, [Variable]s and
|
||||
/// [ResultSet.resolvedColumns] of a select statement.
|
||||
final TypeResolver types = TypeResolver();
|
||||
final TypeResolver types;
|
||||
|
||||
/// Constructs a new analysis context from the AST and the source sql.
|
||||
AnalysisContext(this.root, this.sql);
|
||||
AnalysisContext(this.root, this.sql, EngineOptions options)
|
||||
: types = TypeResolver(options);
|
||||
|
||||
/// Reports an analysis error.
|
||||
void reportError(AnalysisError error) {
|
||||
|
|
|
@ -15,6 +15,9 @@ const _comparisonOperators = [
|
|||
|
||||
class TypeResolver {
|
||||
final Map<Typeable, ResolveResult> _results = {};
|
||||
final EngineOptions options;
|
||||
|
||||
TypeResolver(this.options);
|
||||
|
||||
ResolveResult _cache<T extends Typeable>(
|
||||
ResolveResult Function(T param) resolver, T typeable) {
|
||||
|
@ -157,7 +160,9 @@ class TypeResolver {
|
|||
}, l);
|
||||
}
|
||||
|
||||
/// Expands the parameters
|
||||
/// Returns the expanded parameters of a function [call]. When a
|
||||
/// [StarFunctionParameter] is used, it's expanded to the
|
||||
/// [ReferenceScope.availableColumns].
|
||||
List<Typeable> _expandParameters(Invocation call) {
|
||||
final sqlParameters = call.parameters;
|
||||
if (sqlParameters is ExprFunctionParameters) {
|
||||
|
@ -278,6 +283,34 @@ class TypeResolver {
|
|||
])).withNullable(true);
|
||||
}
|
||||
|
||||
if (options.enableJson1) {
|
||||
switch (call.name.toLowerCase()) {
|
||||
case 'json':
|
||||
case 'json_array':
|
||||
case 'json_insert':
|
||||
case 'json_replace':
|
||||
case 'json_set':
|
||||
case 'json_object':
|
||||
case 'json_patch':
|
||||
case 'json_remove':
|
||||
case 'json_quote':
|
||||
case 'json_group_array':
|
||||
case 'json_group_object':
|
||||
return const ResolveResult(ResolvedType(type: BasicType.text));
|
||||
case 'json_type':
|
||||
return const ResolveResult(
|
||||
ResolvedType(type: BasicType.text, nullable: true));
|
||||
case 'json_valid':
|
||||
return const ResolveResult(ResolvedType.bool());
|
||||
case 'json_extract':
|
||||
// return unknown so that we don't hide the state error. In reality
|
||||
// we have no idea what json_extract returns
|
||||
return const ResolveResult.unknown();
|
||||
case 'json_array_length':
|
||||
return const ResolveResult(ResolvedType(type: BasicType.int));
|
||||
}
|
||||
}
|
||||
|
||||
throw StateError('Unknown function: ${call.name}');
|
||||
}, call);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
class EngineOptions {
|
||||
/// Moor extends the sql grammar a bit to support type converters and other
|
||||
/// features. Enabling this flag will make this engine parse sql with these
|
||||
/// extensions enabled.
|
||||
final bool useMoorExtensions;
|
||||
|
||||
/// Enables functions declared in the `json1` module for analysis
|
||||
final bool enableJson1;
|
||||
|
||||
EngineOptions(this.useMoorExtensions, this.enableJson1);
|
||||
}
|
|
@ -3,6 +3,7 @@ import 'dart:collection';
|
|||
import 'package:sqlparser/src/analysis/analysis.dart';
|
||||
import 'package:sqlparser/src/ast/ast.dart';
|
||||
import 'package:sqlparser/src/engine/autocomplete/engine.dart';
|
||||
import 'package:sqlparser/src/engine/options.dart';
|
||||
import 'package:sqlparser/src/reader/parser/parser.dart';
|
||||
import 'package:sqlparser/src/reader/tokenizer/scanner.dart';
|
||||
import 'package:sqlparser/src/reader/tokenizer/token.dart';
|
||||
|
@ -13,12 +14,11 @@ class SqlEngine {
|
|||
/// All tables registered with [registerTable].
|
||||
final List<Table> knownTables = [];
|
||||
|
||||
/// Moor extends the sql grammar a bit to support type converters and other
|
||||
/// features. Enabling this flag will make this engine parse sql with these
|
||||
/// extensions enabled.
|
||||
final bool useMoorExtensions;
|
||||
/// Internal options for this sql engine.
|
||||
final EngineOptions options;
|
||||
|
||||
SqlEngine({this.useMoorExtensions = false}) {
|
||||
SqlEngine({bool useMoorExtensions = false, bool enableJson1Module = false})
|
||||
: options = EngineOptions(useMoorExtensions, enableJson1Module) {
|
||||
registerTable(sqliteMaster);
|
||||
registerTable(sqliteSequence);
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class SqlEngine {
|
|||
/// you need to filter them. When using the methods in this class, this will
|
||||
/// be taken care of automatically.
|
||||
List<Token> tokenize(String source) {
|
||||
final scanner = Scanner(source, scanMoorTokens: useMoorExtensions);
|
||||
final scanner = Scanner(source, scanMoorTokens: options.useMoorExtensions);
|
||||
final tokens = scanner.scanTokens();
|
||||
|
||||
if (scanner.errors.isNotEmpty) {
|
||||
|
@ -60,7 +60,7 @@ class SqlEngine {
|
|||
ParseResult parse(String sql) {
|
||||
final tokens = tokenize(sql);
|
||||
final tokensForParser = tokens.where((t) => !t.invisibleToParser).toList();
|
||||
final parser = Parser(tokensForParser, useMoor: useMoorExtensions);
|
||||
final parser = Parser(tokensForParser, useMoor: options.useMoorExtensions);
|
||||
|
||||
final stmt = parser.statement();
|
||||
return ParseResult._(stmt, tokens, parser.errors, sql, null);
|
||||
|
@ -69,7 +69,7 @@ class SqlEngine {
|
|||
/// Parses a `.moor` file, which can consist of multiple statements and
|
||||
/// additional components like import statements.
|
||||
ParseResult parseMoorFile(String content) {
|
||||
assert(useMoorExtensions);
|
||||
assert(options.useMoorExtensions);
|
||||
|
||||
final tokens = tokenize(content);
|
||||
final autoComplete = AutoCompleteEngine(tokens);
|
||||
|
@ -104,7 +104,7 @@ class SqlEngine {
|
|||
AnalysisContext analyzeParsed(ParseResult result) {
|
||||
final node = result.rootNode;
|
||||
|
||||
final context = AnalysisContext(node, result.sql);
|
||||
final context = AnalysisContext(node, result.sql, options);
|
||||
_analyzeContext(context);
|
||||
|
||||
return context;
|
||||
|
@ -119,7 +119,7 @@ class SqlEngine {
|
|||
/// and result columns, so all known tables should be registered using
|
||||
/// [registerTable] before calling this method.
|
||||
AnalysisContext analyzeNode(AstNode node, String file) {
|
||||
final context = AnalysisContext(node, file);
|
||||
final context = AnalysisContext(node, file, options);
|
||||
_analyzeContext(context);
|
||||
return context;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import 'package:sqlparser/sqlparser.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('resolves return types of json functions', () {
|
||||
ResolveResult findResult(String expression) {
|
||||
final engine = SqlEngine(enableJson1Module: true);
|
||||
final result = engine.analyze('SELECT $expression;');
|
||||
|
||||
final select = result.root as SelectStatement;
|
||||
final column = select.resultSet.resolvedColumns.single;
|
||||
|
||||
return result.typeOf(column);
|
||||
}
|
||||
|
||||
const resolvedString = ResolveResult(ResolvedType(type: BasicType.text));
|
||||
|
||||
test('create json', () {
|
||||
expect(findResult("json('{}')"), resolvedString);
|
||||
expect(findResult("json_array('foo', 'bar')"), resolvedString);
|
||||
expect(findResult("json_insert('{}')"), resolvedString);
|
||||
expect(findResult("json_replace('{}')"), resolvedString);
|
||||
expect(findResult("json_set('{}')"), resolvedString);
|
||||
expect(findResult('json_object()'), resolvedString);
|
||||
expect(findResult("json_patch('{}', '{}')"), resolvedString);
|
||||
expect(findResult("json_remove('{}', '{}')"), resolvedString);
|
||||
expect(findResult("json_quote('foo')"), resolvedString);
|
||||
expect(findResult('json_group_array()'), resolvedString);
|
||||
expect(findResult('json_group_object()'), resolvedString);
|
||||
});
|
||||
|
||||
test('json_type', () {
|
||||
expect(
|
||||
findResult("json_type('foo', 'bar')"),
|
||||
const ResolveResult(ResolvedType(type: BasicType.text, nullable: true)),
|
||||
);
|
||||
});
|
||||
|
||||
test('json_valid', () {
|
||||
expect(
|
||||
findResult('json_valid()'), const ResolveResult(ResolvedType.bool()));
|
||||
});
|
||||
|
||||
test('json_extract', () {
|
||||
expect(findResult('json_extract()'), const ResolveResult.unknown());
|
||||
});
|
||||
|
||||
test('json_array_length', () {
|
||||
expect(
|
||||
findResult('json_array_length()'),
|
||||
const ResolveResult(ResolvedType(type: BasicType.int)),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue