graphql-persisted_queries 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/rspec.yml +4 -1
- data/CHANGELOG.md +4 -0
- data/README.md +14 -0
- data/Rakefile +29 -1
- data/benchmark/compiled_queries.rb +41 -0
- data/benchmark/helpers.rb +31 -0
- data/benchmark/persisted_queries.rb +41 -0
- data/benchmark/plain_gql.rb +33 -0
- data/docs/compiled_queries_benchmark.md +75 -0
- data/gemfiles/graphql_1_11.gemfile +5 -0
- data/gemfiles/graphql_1_12_0.gemfile +5 -0
- data/gemfiles/graphql_1_12_4.gemfile +5 -0
- data/lib/graphql/persisted_queries.rb +24 -1
- data/lib/graphql/persisted_queries/compiled_queries/multiplex_patch.rb +30 -0
- data/lib/graphql/persisted_queries/compiled_queries/query_patch.rb +33 -0
- data/lib/graphql/persisted_queries/compiled_queries/resolver.rb +38 -0
- data/lib/graphql/persisted_queries/errors.rb +19 -0
- data/lib/graphql/persisted_queries/multiplex_resolver.rb +1 -1
- data/lib/graphql/persisted_queries/resolver.rb +11 -33
- data/lib/graphql/persisted_queries/resolver_helpers.rb +26 -0
- data/lib/graphql/persisted_queries/schema_patch.rb +13 -6
- data/lib/graphql/persisted_queries/store_adapters/base_store_adapter.rb +14 -4
- data/lib/graphql/persisted_queries/version.rb +1 -1
- metadata +15 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18dc641307c696315b6978f4f2e57beaa482fa43db63ab01cdb3eead99ec827c
|
4
|
+
data.tar.gz: a746e4c611a9478c3ced4adc2319db49d4d40e5d56dbefcb345ff871e16b8684
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '08b4dac2681ea25d189bfb51d9e0fe6bbcc44033787d534f2cf02a0a1b1cc2a8cdae0a3e7a4ba5a44bc4d22c43bf5275113ad1b16e30ae2e4269db68e16a0b6d'
|
7
|
+
data.tar.gz: f34db005a3a4c3297a1c682290faaa94b54ff28dcba05140fb8f83cfb0e330fc4b937663f960fbf687a0bf49d97cd06cb0363094a887cf1857cea1397e4b0623
|
data/.github/workflows/rspec.yml
CHANGED
@@ -23,6 +23,9 @@ jobs:
|
|
23
23
|
"gemfiles/graphql_1_8.gemfile",
|
24
24
|
"gemfiles/graphql_1_9.gemfile",
|
25
25
|
"gemfiles/graphql_1_10.gemfile",
|
26
|
+
"gemfiles/graphql_1_11.gemfile",
|
27
|
+
"gemfiles/graphql_1_12_0.gemfile",
|
28
|
+
"gemfiles/graphql_1_12_4.gemfile",
|
26
29
|
"gemfiles/graphql_master.gemfile"
|
27
30
|
]
|
28
31
|
|
@@ -49,4 +52,4 @@ jobs:
|
|
49
52
|
bundle update
|
50
53
|
- name: Run RSpec
|
51
54
|
run: |
|
52
|
-
bundle exec rake
|
55
|
+
bundle exec rake ci_specs
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 1.2.0 (2021-02-24)
|
6
|
+
|
7
|
+
- [PR#39](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries/pull/39) Implement compiled queries ([@DmitryTsepelev][])
|
8
|
+
|
5
9
|
## 1.1.1 (2020-12-03)
|
6
10
|
|
7
11
|
- [PR#37](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries/pull/37) Fix deprecation warnings ([@rbviz][])
|
data/README.md
CHANGED
@@ -65,6 +65,20 @@ GraphqlSchema.execute(
|
|
65
65
|
|
66
66
|
You're all set!
|
67
67
|
|
68
|
+
## Compiled queries (increases performance up to 2x!)
|
69
|
+
|
70
|
+
When query arrives to the backend, GraphQL execution engine needs some time to _parse_ it and build the AST. In case of a huge query it might take [a lot](https://gist.github.com/DmitryTsepelev/36e290cf64b4ec0b18294d0a57fb26ff#file-1_result-md) of time. What if we cache the AST instead of a query text and skip parsing completely? The only thing you need to do is to turn `:compiled_queries` option on:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
class GraphqlSchema < GraphQL::Schema
|
74
|
+
use GraphQL::PersistedQueries, compiled_queries: true
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
Using this option might make your endpoint up to 2x faster according to the [benchmark](docs/compiled_queries_benchmark.md).
|
79
|
+
|
80
|
+
**Heads up!** This feature only works on `graphql-ruby` 1.12.0 or later, but I guess it might be backported.
|
81
|
+
|
68
82
|
## Advanced usage
|
69
83
|
|
70
84
|
All the queries are stored in memory by default, but you can easily switch to another storage (e.g., _redis_:
|
data/Rakefile
CHANGED
@@ -5,4 +5,32 @@ require "rubocop/rake_task"
|
|
5
5
|
RSpec::Core::RakeTask.new(:spec)
|
6
6
|
RuboCop::RakeTask.new
|
7
7
|
|
8
|
-
|
8
|
+
desc "Run specs for compiled queries"
|
9
|
+
RSpec::Core::RakeTask.new("spec:compiled_queries") do |task|
|
10
|
+
task.pattern = "**/compiled_queries/**"
|
11
|
+
task.verbose = false
|
12
|
+
end
|
13
|
+
|
14
|
+
RSpec::Core::RakeTask.new("spec:without_compiled_queries") do |task|
|
15
|
+
task.exclude_pattern = "**/compiled_queries/**"
|
16
|
+
task.verbose = false
|
17
|
+
end
|
18
|
+
|
19
|
+
task ci_specs: ["spec:without_compiled_queries", "spec:compiled_queries"]
|
20
|
+
|
21
|
+
task :bench_gql do
|
22
|
+
cmd = %w[bundle exec ruby benchmark/plain_gql.rb]
|
23
|
+
system(*cmd)
|
24
|
+
end
|
25
|
+
|
26
|
+
task :bench_pq do
|
27
|
+
cmd = %w[bundle exec ruby benchmark/persisted_queries.rb]
|
28
|
+
system(*cmd)
|
29
|
+
end
|
30
|
+
|
31
|
+
task :bench_compiled do
|
32
|
+
cmd = %w[bundle exec ruby benchmark/compiled_queries.rb]
|
33
|
+
system(*cmd)
|
34
|
+
end
|
35
|
+
|
36
|
+
task bench: [:bench_gql, :bench_pq, :bench_compiled]
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "bundler/inline"
|
2
|
+
|
3
|
+
gemfile do
|
4
|
+
source "https://rubygems.org"
|
5
|
+
gem "graphql", "1.12.4"
|
6
|
+
end
|
7
|
+
|
8
|
+
$:.push File.expand_path("../lib", __dir__)
|
9
|
+
|
10
|
+
require "benchmark"
|
11
|
+
require "graphql/persisted_queries"
|
12
|
+
require_relative "helpers"
|
13
|
+
|
14
|
+
class GraphqlSchema < GraphQL::Schema
|
15
|
+
use GraphQL::PersistedQueries, compiled_queries: true
|
16
|
+
|
17
|
+
query QueryType
|
18
|
+
end
|
19
|
+
|
20
|
+
GraphqlSchema.to_definition
|
21
|
+
|
22
|
+
puts
|
23
|
+
puts "Schema with compiled queries:"
|
24
|
+
puts
|
25
|
+
|
26
|
+
Benchmark.bm(28) do |x|
|
27
|
+
[false, true].each do |with_nested|
|
28
|
+
FIELD_COUNTS.each do |field_count|
|
29
|
+
query = generate_query(field_count, with_nested)
|
30
|
+
sha256 = Digest::SHA256.hexdigest(query)
|
31
|
+
|
32
|
+
context = { extensions: { "persistedQuery" => { "sha256Hash" => sha256 } } }
|
33
|
+
# warmup
|
34
|
+
GraphqlSchema.execute(query, context: context)
|
35
|
+
|
36
|
+
x.report("#{field_count} fields#{" (nested)" if with_nested}") do
|
37
|
+
GraphqlSchema.execute(query, context: context)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
FIELD_COUNTS = [10, 50, 100, 200, 300]
|
2
|
+
|
3
|
+
def generate_fields(field_count, with_nested)
|
4
|
+
fields = field_count.times.map do |i|
|
5
|
+
field = "field#{i+1}"
|
6
|
+
field += "\s{#{generate_fields(field_count, false)}}" if with_nested
|
7
|
+
field
|
8
|
+
end
|
9
|
+
|
10
|
+
fields.join("\n")
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate_query(field_count, with_nested)
|
14
|
+
<<-gql
|
15
|
+
query {
|
16
|
+
#{generate_fields(field_count, with_nested)}
|
17
|
+
}
|
18
|
+
gql
|
19
|
+
end
|
20
|
+
|
21
|
+
class ChildType < GraphQL::Schema::Object
|
22
|
+
FIELD_COUNTS.max.times do |i|
|
23
|
+
field "field#{i + 1}".to_sym, String, null: false, method: :itself
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class QueryType < GraphQL::Schema::Object
|
28
|
+
FIELD_COUNTS.max.times do |i|
|
29
|
+
field "field#{i + 1}".to_sym, ChildType, null: false, method: :itself
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "bundler/inline"
|
2
|
+
|
3
|
+
gemfile do
|
4
|
+
source "https://rubygems.org"
|
5
|
+
gem "graphql", "1.12.4"
|
6
|
+
end
|
7
|
+
|
8
|
+
$:.push File.expand_path("../lib", __dir__)
|
9
|
+
|
10
|
+
require "benchmark"
|
11
|
+
require "graphql/persisted_queries"
|
12
|
+
require_relative "helpers"
|
13
|
+
|
14
|
+
class GraphqlSchema < GraphQL::Schema
|
15
|
+
use GraphQL::PersistedQueries
|
16
|
+
|
17
|
+
query QueryType
|
18
|
+
end
|
19
|
+
|
20
|
+
GraphqlSchema.to_definition
|
21
|
+
|
22
|
+
puts
|
23
|
+
puts "Schema with persisted queries:"
|
24
|
+
puts
|
25
|
+
|
26
|
+
Benchmark.bm(28) do |x|
|
27
|
+
[false, true].each do |with_nested|
|
28
|
+
FIELD_COUNTS.each do |field_count|
|
29
|
+
query = generate_query(field_count, with_nested)
|
30
|
+
sha256 = Digest::SHA256.hexdigest(query)
|
31
|
+
context = { extensions: { "persistedQuery" => { "sha256Hash" => sha256 } } }
|
32
|
+
|
33
|
+
# warmup
|
34
|
+
GraphqlSchema.execute(query, context: context)
|
35
|
+
|
36
|
+
x.report("#{field_count} fields#{" (nested)" if with_nested}") do
|
37
|
+
GraphqlSchema.execute(query, context: context)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "bundler/inline"
|
2
|
+
|
3
|
+
gemfile do
|
4
|
+
source "https://rubygems.org"
|
5
|
+
gem "graphql", "1.12.4"
|
6
|
+
end
|
7
|
+
|
8
|
+
$:.push File.expand_path("../lib", __dir__)
|
9
|
+
|
10
|
+
require "benchmark"
|
11
|
+
require "graphql/persisted_queries"
|
12
|
+
require_relative "helpers"
|
13
|
+
|
14
|
+
class GraphqlSchema < GraphQL::Schema
|
15
|
+
query QueryType
|
16
|
+
end
|
17
|
+
|
18
|
+
GraphqlSchema.to_definition
|
19
|
+
|
20
|
+
puts "Plain schema:"
|
21
|
+
puts
|
22
|
+
|
23
|
+
Benchmark.bm(28) do |x|
|
24
|
+
[false, true].each do |with_nested|
|
25
|
+
FIELD_COUNTS.each do |field_count|
|
26
|
+
query = generate_query(field_count, with_nested)
|
27
|
+
|
28
|
+
x.report("#{field_count} fields#{" (nested)" if with_nested}") do
|
29
|
+
GraphqlSchema.execute(query)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# Compiled queries benchmarks
|
2
|
+
|
3
|
+
The name of benchmark consists of a field count and optional "nested" label. In case of non–nested one we just generate a query with that field count, e.g. `2 fields` means:
|
4
|
+
|
5
|
+
```gql
|
6
|
+
query {
|
7
|
+
field1
|
8
|
+
field2
|
9
|
+
}
|
10
|
+
```
|
11
|
+
|
12
|
+
In case of "nested" benchmark we also put a list of fields to each top–level field, e.g. `2 fields (nested)` means:
|
13
|
+
|
14
|
+
```gql
|
15
|
+
query {
|
16
|
+
field1 {
|
17
|
+
field1
|
18
|
+
field2
|
19
|
+
}
|
20
|
+
field2 {
|
21
|
+
field1
|
22
|
+
field2
|
23
|
+
}
|
24
|
+
}
|
25
|
+
```
|
26
|
+
|
27
|
+
Field resolver just returns a string, so real–world tests might be way slower because of IO.
|
28
|
+
|
29
|
+
Here are the results:
|
30
|
+
|
31
|
+
```
|
32
|
+
Plain schema:
|
33
|
+
|
34
|
+
user system total real
|
35
|
+
10 fields 0.001061 0.000039 0.001100 ( 0.001114)
|
36
|
+
50 fields 0.001658 0.000003 0.001661 ( 0.001661)
|
37
|
+
100 fields 0.004587 0.000026 0.004613 ( 0.004614)
|
38
|
+
200 fields 0.006447 0.000016 0.006463 ( 0.006476)
|
39
|
+
300 fields 0.024493 0.000073 0.024566 ( 0.024614)
|
40
|
+
10 fields (nested) 0.003061 0.000043 0.003104 ( 0.003109)
|
41
|
+
50 fields (nested) 0.056927 0.000995 0.057922 ( 0.057997)
|
42
|
+
100 fields (nested) 0.245235 0.001336 0.246571 ( 0.246727)
|
43
|
+
200 fields (nested) 0.974444 0.006531 0.980975 ( 0.981810)
|
44
|
+
300 fields (nested) 2.175855 0.012773 2.188628 ( 2.190130)
|
45
|
+
|
46
|
+
Schema with persisted queries:
|
47
|
+
|
48
|
+
user system total real
|
49
|
+
10 fields 0.000606 0.000007 0.000613 ( 0.000607)
|
50
|
+
50 fields 0.001855 0.000070 0.001925 ( 0.001915)
|
51
|
+
100 fields 0.003239 0.000009 0.003248 ( 0.003239)
|
52
|
+
200 fields 0.007542 0.000009 0.007551 ( 0.007551)
|
53
|
+
300 fields 0.014975 0.000237 0.015212 ( 0.015318)
|
54
|
+
10 fields (nested) 0.002992 0.000068 0.003060 ( 0.003049)
|
55
|
+
50 fields (nested) 0.062314 0.000274 0.062588 ( 0.062662)
|
56
|
+
100 fields (nested) 0.256404 0.000865 0.257269 ( 0.257419)
|
57
|
+
200 fields (nested) 0.978408 0.007437 0.985845 ( 0.986579)
|
58
|
+
300 fields (nested) 2.263338 0.010994 2.274332 ( 2.275967)
|
59
|
+
|
60
|
+
Schema with compiled queries:
|
61
|
+
|
62
|
+
user system total real
|
63
|
+
10 fields 0.000526 0.000009 0.000535 ( 0.000530)
|
64
|
+
50 fields 0.001280 0.000012 0.001292 ( 0.001280)
|
65
|
+
100 fields 0.002292 0.000004 0.002296 ( 0.002286)
|
66
|
+
200 fields 0.005462 0.000001 0.005463 ( 0.005463)
|
67
|
+
300 fields 0.014229 0.000121 0.014350 ( 0.014348)
|
68
|
+
10 fields (nested) 0.002027 0.000069 0.002096 ( 0.002104)
|
69
|
+
50 fields (nested) 0.029933 0.000087 0.030020 ( 0.030040)
|
70
|
+
100 fields (nested) 0.133933 0.000502 0.134435 ( 0.134756)
|
71
|
+
200 fields (nested) 0.495052 0.003545 0.498597 ( 0.499452)
|
72
|
+
300 fields (nested) 1.041463 0.005130 1.046593 ( 1.047137)
|
73
|
+
```
|
74
|
+
|
75
|
+
Results gathered from my MacBook Pro Mid 2014 (2,5 GHz Quad-Core Intel Core i7, 16 GB 1600 MHz DDR3).
|
@@ -1,17 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "graphql/persisted_queries/resolver_helpers"
|
4
|
+
require "graphql/persisted_queries/errors"
|
3
5
|
require "graphql/persisted_queries/error_handlers"
|
4
6
|
require "graphql/persisted_queries/schema_patch"
|
5
7
|
require "graphql/persisted_queries/store_adapters"
|
6
8
|
require "graphql/persisted_queries/version"
|
7
9
|
require "graphql/persisted_queries/builder_helpers"
|
8
10
|
|
11
|
+
require "graphql/persisted_queries/compiled_queries/resolver"
|
12
|
+
require "graphql/persisted_queries/compiled_queries/multiplex_patch"
|
13
|
+
require "graphql/persisted_queries/compiled_queries/query_patch"
|
14
|
+
|
9
15
|
module GraphQL
|
10
16
|
# Plugin definition
|
11
17
|
module PersistedQueries
|
18
|
+
# rubocop:disable Metrics/MethodLength
|
12
19
|
def self.use(schema_defn, **options)
|
13
20
|
schema = schema_defn.is_a?(Class) ? schema_defn : schema_defn.target
|
14
|
-
|
21
|
+
|
22
|
+
compiled_queries = options.delete(:compiled_queries)
|
23
|
+
SchemaPatch.patch(schema, compiled_queries)
|
24
|
+
configure_compiled_queries if compiled_queries
|
15
25
|
|
16
26
|
schema.hash_generator = options.delete(:hash_generator) || :sha256
|
17
27
|
|
@@ -25,5 +35,18 @@ module GraphQL
|
|
25
35
|
store = options.delete(:store) || :memory
|
26
36
|
schema.configure_persisted_query_store(store, **options)
|
27
37
|
end
|
38
|
+
# rubocop:enable Metrics/MethodLength
|
39
|
+
|
40
|
+
def self.configure_compiled_queries
|
41
|
+
if Gem::Dependency.new("graphql", "< 1.12.0").match?("graphql", GraphQL::VERSION)
|
42
|
+
raise ArgumentError, "compiled_queries are not supported for graphql-ruby < 1.12.0"
|
43
|
+
end
|
44
|
+
|
45
|
+
GraphQL::Execution::Multiplex.singleton_class.prepend(
|
46
|
+
GraphQL::PersistedQueries::CompiledQueries::MultiplexPatch
|
47
|
+
)
|
48
|
+
|
49
|
+
GraphQL::Query.prepend(GraphQL::PersistedQueries::CompiledQueries::QueryPatch)
|
50
|
+
end
|
28
51
|
end
|
29
52
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module PersistedQueries
|
5
|
+
module CompiledQueries
|
6
|
+
# Patches GraphQL::Execution::Multiplex to support compiled queries
|
7
|
+
module MultiplexPatch
|
8
|
+
if Gem::Dependency.new("graphql", ">= 1.12.4").match?("graphql", GraphQL::VERSION)
|
9
|
+
def begin_query(results, idx, query, multiplex)
|
10
|
+
return super unless query.persisted_query_not_found?
|
11
|
+
|
12
|
+
results[idx] = add_not_found_error(query)
|
13
|
+
end
|
14
|
+
else
|
15
|
+
def begin_query(query, multiplex)
|
16
|
+
return super unless query.persisted_query_not_found?
|
17
|
+
|
18
|
+
add_not_found_error(query)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_not_found_error(query)
|
23
|
+
query.context.errors.clear
|
24
|
+
query.context.errors << GraphQL::ExecutionError.new("PersistedQueryNotFound")
|
25
|
+
GraphQL::Execution::Multiplex::NO_OPERATION
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module PersistedQueries
|
5
|
+
module CompiledQueries
|
6
|
+
# Patches GraphQL::Query to support compiled queries
|
7
|
+
module QueryPatch
|
8
|
+
def persisted_query_not_found?
|
9
|
+
@persisted_query_not_found
|
10
|
+
end
|
11
|
+
|
12
|
+
def prepare_ast
|
13
|
+
return super unless @context[:extensions]
|
14
|
+
|
15
|
+
@document = resolver.fetch
|
16
|
+
not_loaded_document = @document.nil?
|
17
|
+
|
18
|
+
@persisted_query_not_found = not_loaded_document && query_string.nil?
|
19
|
+
|
20
|
+
super.tap do
|
21
|
+
resolver.persist(query_string, @document) if not_loaded_document && query_string
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def resolver
|
28
|
+
@resolver ||= Resolver.new(@schema, @context[:extensions])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module PersistedQueries
|
5
|
+
module CompiledQueries
|
6
|
+
# Fetches and persists compiled query
|
7
|
+
class Resolver
|
8
|
+
include GraphQL::PersistedQueries::ResolverHelpers
|
9
|
+
|
10
|
+
def initialize(schema, extensions)
|
11
|
+
@schema = schema
|
12
|
+
@extensions = extensions
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch
|
16
|
+
return if hash.nil?
|
17
|
+
|
18
|
+
with_error_handling do
|
19
|
+
compiled_query = @schema.persisted_query_store.fetch_query(hash, compiled_query: true)
|
20
|
+
Marshal.load(compiled_query) if compiled_query # rubocop:disable Security/MarshalLoad
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def persist(query_string, compiled_query)
|
25
|
+
return if hash.nil?
|
26
|
+
|
27
|
+
validate_hash!(query_string)
|
28
|
+
|
29
|
+
with_error_handling do
|
30
|
+
@schema.persisted_query_store.save_query(
|
31
|
+
hash, Marshal.dump(compiled_query), compiled_query: true
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module PersistedQueries
|
5
|
+
# Raised when persisted query is not found in the storage
|
6
|
+
class NotFound < StandardError
|
7
|
+
def message
|
8
|
+
"PersistedQueryNotFound"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Raised when provided hash is not matched with query
|
13
|
+
class WrongHash < StandardError
|
14
|
+
def message
|
15
|
+
"Wrong hash was passed"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -33,7 +33,7 @@ module GraphQL
|
|
33
33
|
return unless extensions
|
34
34
|
|
35
35
|
query_params[:query] = Resolver.new(extensions, @schema).resolve(query_params[:query])
|
36
|
-
rescue
|
36
|
+
rescue GraphQL::PersistedQueries::NotFound, GraphQL::PersistedQueries::WrongHash => e
|
37
37
|
values = { "errors" => [{ "message" => e.message }] }
|
38
38
|
query = GraphQL::Query.new(@schema, query_params[:query])
|
39
39
|
results[pos] = GraphQL::Query::Result.new(query: query, values: values)
|
@@ -4,54 +4,32 @@ module GraphQL
|
|
4
4
|
module PersistedQueries
|
5
5
|
# Fetches or stores query string in the storage
|
6
6
|
class Resolver
|
7
|
-
|
8
|
-
class NotFound < StandardError
|
9
|
-
def message
|
10
|
-
"PersistedQueryNotFound"
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
# Raised when provided hash is not matched with query
|
15
|
-
class WrongHash < StandardError
|
16
|
-
def message
|
17
|
-
"Wrong hash was passed"
|
18
|
-
end
|
19
|
-
end
|
7
|
+
include GraphQL::PersistedQueries::ResolverHelpers
|
20
8
|
|
21
9
|
def initialize(extensions, schema)
|
22
10
|
@extensions = extensions
|
23
11
|
@schema = schema
|
24
12
|
end
|
25
13
|
|
26
|
-
def resolve(
|
27
|
-
return
|
14
|
+
def resolve(query_string)
|
15
|
+
return query_string if hash.nil?
|
28
16
|
|
29
|
-
if
|
30
|
-
persist_query(
|
17
|
+
if query_string
|
18
|
+
persist_query(query_string)
|
31
19
|
else
|
32
|
-
|
33
|
-
raise NotFound if
|
20
|
+
query_string = with_error_handling { @schema.persisted_query_store.fetch_query(hash) }
|
21
|
+
raise GraphQL::PersistedQueries::NotFound if query_string.nil?
|
34
22
|
end
|
35
23
|
|
36
|
-
|
24
|
+
query_string
|
37
25
|
end
|
38
26
|
|
39
27
|
private
|
40
28
|
|
41
|
-
def
|
42
|
-
|
43
|
-
rescue StandardError => e
|
44
|
-
@schema.persisted_query_error_handler.call(e)
|
45
|
-
end
|
46
|
-
|
47
|
-
def persist_query(query_str)
|
48
|
-
raise WrongHash if @schema.hash_generator_proc.call(query_str) != hash
|
49
|
-
|
50
|
-
with_error_handling { @schema.persisted_query_store.save_query(hash, query_str) }
|
51
|
-
end
|
29
|
+
def persist_query(query_string)
|
30
|
+
validate_hash!(query_string)
|
52
31
|
|
53
|
-
|
54
|
-
@hash ||= @extensions.dig("persistedQuery", "sha256Hash")
|
32
|
+
with_error_handling { @schema.persisted_query_store.save_query(hash, query_string) }
|
55
33
|
end
|
56
34
|
end
|
57
35
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module PersistedQueries
|
5
|
+
# Helper functions for resolvers
|
6
|
+
module ResolverHelpers
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def with_error_handling
|
10
|
+
yield
|
11
|
+
rescue StandardError => e
|
12
|
+
@schema.persisted_query_error_handler.call(e)
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate_hash!(query_string)
|
16
|
+
return if @schema.hash_generator_proc.call(query_string) == hash
|
17
|
+
|
18
|
+
raise GraphQL::PersistedQueries::WrongHash
|
19
|
+
end
|
20
|
+
|
21
|
+
def hash
|
22
|
+
@hash ||= @extensions.dig("persistedQuery", "sha256Hash")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -10,9 +10,20 @@ module GraphQL
|
|
10
10
|
# Patches GraphQL::Schema to support persisted queries
|
11
11
|
module SchemaPatch
|
12
12
|
class << self
|
13
|
-
def patch(schema)
|
14
|
-
schema.singleton_class.class_eval { alias_method :multiplex_original, :multiplex }
|
13
|
+
def patch(schema, compiled_queries)
|
15
14
|
schema.singleton_class.prepend(SchemaPatch)
|
15
|
+
|
16
|
+
return if compiled_queries
|
17
|
+
|
18
|
+
schema.singleton_class.class_eval { alias_method :multiplex_original, :multiplex }
|
19
|
+
schema.singleton_class.prepend(MultiplexPatch)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Patches GraphQL::Schema to override multiplex (not needed for compiled queries)
|
24
|
+
module MultiplexPatch
|
25
|
+
def multiplex(queries, **kwargs)
|
26
|
+
MultiplexResolver.new(self, queries, **kwargs).resolve
|
16
27
|
end
|
17
28
|
end
|
18
29
|
|
@@ -47,10 +58,6 @@ module GraphQL
|
|
47
58
|
@persisted_queries_tracing_enabled
|
48
59
|
end
|
49
60
|
|
50
|
-
def multiplex(queries, **kwargs)
|
51
|
-
MultiplexResolver.new(self, queries, **kwargs).resolve
|
52
|
-
end
|
53
|
-
|
54
61
|
def tracer(name)
|
55
62
|
super.tap do
|
56
63
|
# Since tracers can be set before *and* after our plugin hooks in,
|
@@ -12,15 +12,18 @@ module GraphQL
|
|
12
12
|
@name = :base
|
13
13
|
end
|
14
14
|
|
15
|
-
def fetch_query(hash)
|
16
|
-
|
15
|
+
def fetch_query(hash, compiled_query: false)
|
16
|
+
key = build_key(hash, compiled_query)
|
17
|
+
|
18
|
+
fetch(key).tap do |result|
|
17
19
|
event = result ? "cache_hit" : "cache_miss"
|
18
20
|
trace("fetch_query.#{event}", adapter: @name)
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
|
-
def save_query(hash, query)
|
23
|
-
|
24
|
+
def save_query(hash, query, compiled_query: false)
|
25
|
+
key = build_key(hash, compiled_query)
|
26
|
+
trace("save_query", adapter: @name) { save(key, query) }
|
24
27
|
end
|
25
28
|
|
26
29
|
protected
|
@@ -41,6 +44,13 @@ module GraphQL
|
|
41
44
|
yield
|
42
45
|
end
|
43
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def build_key(hash, compiled_query)
|
51
|
+
key = "#{RUBY_ENGINE}-#{RUBY_VERSION}:#{GraphQL::VERSION}:#{hash}"
|
52
|
+
compiled_query ? "compiled:#{key}" : key
|
53
|
+
end
|
44
54
|
end
|
45
55
|
end
|
46
56
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-persisted_queries
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- DmitryTsepelev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -124,14 +124,22 @@ files:
|
|
124
124
|
- LICENSE.txt
|
125
125
|
- README.md
|
126
126
|
- Rakefile
|
127
|
+
- benchmark/compiled_queries.rb
|
128
|
+
- benchmark/helpers.rb
|
129
|
+
- benchmark/persisted_queries.rb
|
130
|
+
- benchmark/plain_gql.rb
|
127
131
|
- bin/console
|
128
132
|
- bin/setup
|
129
133
|
- docs/alternative_stores.md
|
134
|
+
- docs/compiled_queries_benchmark.md
|
130
135
|
- docs/error_handling.md
|
131
136
|
- docs/hash.md
|
132
137
|
- docs/http_cache.md
|
133
138
|
- docs/tracing.md
|
134
139
|
- gemfiles/graphql_1_10.gemfile
|
140
|
+
- gemfiles/graphql_1_11.gemfile
|
141
|
+
- gemfiles/graphql_1_12_0.gemfile
|
142
|
+
- gemfiles/graphql_1_12_4.gemfile
|
135
143
|
- gemfiles/graphql_1_8.gemfile
|
136
144
|
- gemfiles/graphql_1_9.gemfile
|
137
145
|
- gemfiles/graphql_master.gemfile
|
@@ -141,12 +149,17 @@ files:
|
|
141
149
|
- lib/graphql/persisted_queries/analyzers/http_method_ast_analyzer.rb
|
142
150
|
- lib/graphql/persisted_queries/analyzers/http_method_validator.rb
|
143
151
|
- lib/graphql/persisted_queries/builder_helpers.rb
|
152
|
+
- lib/graphql/persisted_queries/compiled_queries/multiplex_patch.rb
|
153
|
+
- lib/graphql/persisted_queries/compiled_queries/query_patch.rb
|
154
|
+
- lib/graphql/persisted_queries/compiled_queries/resolver.rb
|
144
155
|
- lib/graphql/persisted_queries/error_handlers.rb
|
145
156
|
- lib/graphql/persisted_queries/error_handlers/base_error_handler.rb
|
146
157
|
- lib/graphql/persisted_queries/error_handlers/default_error_handler.rb
|
158
|
+
- lib/graphql/persisted_queries/errors.rb
|
147
159
|
- lib/graphql/persisted_queries/hash_generator_builder.rb
|
148
160
|
- lib/graphql/persisted_queries/multiplex_resolver.rb
|
149
161
|
- lib/graphql/persisted_queries/resolver.rb
|
162
|
+
- lib/graphql/persisted_queries/resolver_helpers.rb
|
150
163
|
- lib/graphql/persisted_queries/schema_patch.rb
|
151
164
|
- lib/graphql/persisted_queries/store_adapters.rb
|
152
165
|
- lib/graphql/persisted_queries/store_adapters/base_store_adapter.rb
|