diff --git a/lib/jmespath.rb b/lib/jmespath.rb index 3c2e269..bbb13bb 100644 --- a/lib/jmespath.rb +++ b/lib/jmespath.rb @@ -10,6 +10,7 @@ module JMESPath require 'jmespath/nodes' require 'jmespath/parser' require 'jmespath/runtime' + require 'jmespath/caching_runtime' require 'jmespath/token' require 'jmespath/token_stream' require 'jmespath/util' @@ -28,7 +29,8 @@ def search(expression, data, runtime_options = {}) when IO, StringIO then JSON.parse(data.read) else data end - Runtime.new(runtime_options).search(expression, data) + runtime = runtime_options.empty? ? DEFAULT_RUNTIME : Runtime.new(runtime_options) + runtime.search(expression, data) end # @api private @@ -36,4 +38,6 @@ def load_json(path) JSON.parse(File.open(path, 'r', encoding: 'UTF-8', &:read)) end end + + DEFAULT_RUNTIME = CachingRuntime.new end diff --git a/lib/jmespath/caching_runtime.rb b/lib/jmespath/caching_runtime.rb new file mode 100644 index 0000000..bbc2ca1 --- /dev/null +++ b/lib/jmespath/caching_runtime.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true +require 'thread' + +module JMESPath + # @api private + class CachingRuntime < Runtime + def initialize(options = {}) + super(options.merge(cache_expressions: false)) + @mutex = Mutex.new + @cache = {} + end + + def search(expression, data) + if cached = @cache[expression] + cached.visit(data) + else + cache_expression(expression).visit(data) + end + end + + private + + def cache_expression(expression) + @mutex.synchronize do + @cache.clear if @cache.size > 1000 + @cache[expression] ||= @parser.parse(expression).optimize + end + end + end +end