From 5268d8834427f8f647ae3da132cc8fe45341288e Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sun, 26 Jan 2020 14:22:07 +0100 Subject: [PATCH] Extract json1 support into an extension --- moor_generator/lib/src/analyzer/session.dart | 2 +- sqlparser/CHANGELOG.md | 3 + sqlparser/lib/sqlparser.dart | 1 + .../lib/src/analysis/types/resolver.dart | 28 ----- .../analysis/types2/resolving_visitor.dart | 31 +---- sqlparser/lib/src/engine/module/json1.dart | 70 ++++++++++++ sqlparser/lib/src/engine/options.dart | 4 - sqlparser/lib/src/engine/sql_engine.dart | 4 +- .../test/analysis/json_functions_test.dart | 107 ++++++++++-------- 9 files changed, 140 insertions(+), 110 deletions(-) create mode 100644 sqlparser/lib/src/engine/module/json1.dart diff --git a/moor_generator/lib/src/analyzer/session.dart b/moor_generator/lib/src/analyzer/session.dart index 350586f1..edebc787 100644 --- a/moor_generator/lib/src/analyzer/session.dart +++ b/moor_generator/lib/src/analyzer/session.dart @@ -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, ); diff --git a/sqlparser/CHANGELOG.md b/sqlparser/CHANGELOG.md index fe96f798..97bea167 100644 --- a/sqlparser/CHANGELOG.md +++ b/sqlparser/CHANGELOG.md @@ -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 diff --git a/sqlparser/lib/sqlparser.dart b/sqlparser/lib/sqlparser.dart index a851e875..27b7b5d6 100644 --- a/sqlparser/lib/sqlparser.dart +++ b/sqlparser/lib/sqlparser.dart @@ -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'; diff --git a/sqlparser/lib/src/analysis/types/resolver.dart b/sqlparser/lib/src/analysis/types/resolver.dart index 902aa41d..ca3bccda 100644 --- a/sqlparser/lib/src/analysis/types/resolver.dart +++ b/sqlparser/lib/src/analysis/types/resolver.dart @@ -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); diff --git a/sqlparser/lib/src/analysis/types2/resolving_visitor.dart b/sqlparser/lib/src/analysis/types2/resolving_visitor.dart index f51566d6..afe5f2ae 100644 --- a/sqlparser/lib/src/analysis/types2/resolving_visitor.dart +++ b/sqlparser/lib/src/analysis/types2/resolving_visitor.dart @@ -510,34 +510,9 @@ class TypeResolver extends RecursiveVisitor { 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; } diff --git a/sqlparser/lib/src/engine/module/json1.dart b/sqlparser/lib/src/engine/module/json1.dart new file mode 100644 index 00000000..646b4cff --- /dev/null +++ b/sqlparser/lib/src/engine/module/json1.dart @@ -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 _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 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 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) {} +} diff --git a/sqlparser/lib/src/engine/options.dart b/sqlparser/lib/src/engine/options.dart index 48286f1b..21755151 100644 --- a/sqlparser/lib/src/engine/options.dart +++ b/sqlparser/lib/src/engine/options.dart @@ -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, }); diff --git a/sqlparser/lib/src/engine/sql_engine.dart b/sqlparser/lib/src/engine/sql_engine.dart index 49426b06..b476856f 100644 --- a/sqlparser/lib/src/engine/sql_engine.dart +++ b/sqlparser/lib/src/engine/sql_engine.dart @@ -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, ); } diff --git a/sqlparser/test/analysis/json_functions_test.dart b/sqlparser/test/analysis/json_functions_test.dart index 3f2f8ce8..669175dc 100644 --- a/sqlparser/test/analysis/json_functions_test.dart +++ b/sqlparser/test/analysis/json_functions_test.dart @@ -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)), + ); }); }