Extract json1 support into an extension

This commit is contained in:
Simon Binder 2020-01-26 14:22:07 +01:00
parent c82bff8c97
commit 5268d88344
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
9 changed files with 140 additions and 110 deletions

View File

@ -38,9 +38,9 @@ class MoorSession {
SqlEngine spawnEngine() {
final sqlOptions = EngineOptions(
useMoorExtensions: true,
enableJson1: options.hasModule(SqlModule.json1),
enabledExtensions: [
if (options.hasModule(SqlModule.fts5)) const Fts5Extension(),
if (options.hasModule(SqlModule.json1)) const Json1Extension(),
],
enableExperimentalTypeInference: options.useExperimentalInference,
);

View File

@ -1,5 +1,8 @@
## unreleased
- New feature: Table valued functions.
- __Breaking__: Removed the `enableJson1` parameter on `EngineOptions`. Add a `Json1Extension` instance
to `enabledExtensions` instead.
- Parse `rowid` as a valid reference when needed (`SELECT rowid FROM tbl` is now parsed correctly)
## 0.6.0

View File

@ -4,6 +4,7 @@ library sqlparser;
export 'src/analysis/analysis.dart';
export 'src/ast/ast.dart';
export 'src/engine/module/fts5.dart' show Fts5Extension;
export 'src/engine/module/json1.dart' show Json1Extension;
export 'src/engine/module/module.dart';
export 'src/engine/options.dart';
export 'src/engine/sql_engine.dart';

View File

@ -277,34 +277,6 @@ 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));
}
}
if (options.addedFunctions.containsKey(lowercaseName)) {
return options.addedFunctions[lowercaseName]
.inferReturnType(context, call, parameters);

View File

@ -510,34 +510,9 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
return null;
}
final options = session.options;
if (options.enableJson1) {
switch (lowercaseName) {
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 _textType;
case 'json_type':
return _textType;
case 'json_valid':
return const ResolvedType.bool();
case 'json_extract':
return null;
case 'json_array_length':
return _intType;
}
}
if (options.addedFunctions.containsKey(lowercaseName)) {
return options.addedFunctions[lowercaseName]
final addedFunctions = session.options.addedFunctions;
if (addedFunctions.containsKey(lowercaseName)) {
return addedFunctions[lowercaseName]
.inferReturnType(session.context, e, params)
?.type;
}

View File

@ -0,0 +1,70 @@
import 'package:sqlparser/sqlparser.dart';
class Json1Extension implements Extension {
const Json1Extension();
@override
void register(SqlEngine engine) {
engine.registerFunctionHandler(const _Json1Functions());
}
}
class _Json1Functions implements FunctionHandler {
const _Json1Functions();
static const Set<String> _returnStrings = {
'json',
'json_array',
'json_insert',
'json_replace',
'json_set',
'json_object',
'json_patch',
'json_remove',
'json_quote',
'json_group_array',
'json_group_object',
};
@override
Set<String> get functionNames => const {
..._returnStrings,
'json_type',
'json_valid',
'json_extract',
'json_array_length',
};
@override
ResolveResult inferArgumentType(
AnalysisContext context, SqlInvocation call, Expression argument) {
return const ResolveResult.unknown();
}
@override
ResolveResult inferReturnType(AnalysisContext context, SqlInvocation call,
List<Typeable> expandedArgs) {
final name = call.name.toLowerCase();
if (_returnStrings.contains(name)) {
return const ResolveResult(ResolvedType(type: BasicType.text));
} else {
switch (name) {
case 'json_type':
return const ResolveResult(
ResolvedType(type: BasicType.text, nullable: true));
case 'json_valid':
return const ResolveResult(ResolvedType.bool());
case 'json_extract':
return const ResolveResult.unknown();
case 'json_array_length':
return const ResolveResult(ResolvedType(type: BasicType.int));
}
}
throw AssertionError("Can't happen, unknown json1 function");
}
@override
void reportErrors(SqlInvocation call, AnalysisContext context) {}
}

View File

@ -6,9 +6,6 @@ class EngineOptions {
/// extensions enabled.
final bool useMoorExtensions;
/// Enables functions declared in the `json1` module for analysis
final bool enableJson1;
/// Enables the new, experimental type inference.
final bool enableExperimentalTypeInference;
@ -26,7 +23,6 @@ class EngineOptions {
EngineOptions({
this.useMoorExtensions = false,
this.enableJson1 = false,
this.enabledExtensions = const [],
this.enableExperimentalTypeInference = false,
});

View File

@ -3,6 +3,7 @@ import 'dart:collection';
import 'package:sqlparser/sqlparser.dart';
import 'package:sqlparser/src/analysis/types2/types.dart' as t2;
import 'package:sqlparser/src/engine/module/fts5.dart';
import 'package:sqlparser/src/engine/module/json1.dart';
import 'package:sqlparser/src/engine/options.dart';
import 'package:sqlparser/src/reader/parser/parser.dart';
import 'package:sqlparser/src/reader/tokenizer/scanner.dart';
@ -234,10 +235,11 @@ class SqlEngine {
static EngineOptions _constructOptions({bool moor, bool fts5, bool json1}) {
final extensions = [
if (fts5) const Fts5Extension(),
if (json1) const Json1Extension(),
];
return EngineOptions(
useMoorExtensions: moor,
enableJson1: json1,
enabledExtensions: extensions,
);
}

View File

@ -2,54 +2,65 @@ 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.withOptions(EngineOptions(enableJson1: true));
final result = engine.analyze('SELECT $expression;');
group('json with stable inference', () {
_runTests(false);
});
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)),
);
});
group('json with types2', () {
_runTests(true);
});
}
void _runTests(bool types2) {
ResolveResult findResult(String expression) {
final engine = SqlEngine.withOptions(EngineOptions(
enableExperimentalTypeInference: types2,
enabledExtensions: const [Json1Extension()],
));
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)),
);
});
}