monster-queries 1.3.3
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +34 -0
- data/app/queries/pagination/offset.sql +7 -0
- data/app/queries/pagination/select.sql +12 -0
- data/config/initializers/query.rb +4 -0
- data/config/routes.rb +2 -0
- data/lib/monster-queries.rb +6 -0
- data/lib/monster_queries/action_controller.rb +55 -0
- data/lib/monster_queries/active_record.rb +101 -0
- data/lib/monster_queries/builder.rb +49 -0
- data/lib/monster_queries/engine.rb +4 -0
- data/lib/monster_queries/helpers.rb +4 -0
- data/lib/monster_queries/query.rb +147 -0
- data/lib/monster_queries/version.rb +3 -0
- data/lib/tasks/monster_queries_tasks.rake +4 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 85f1857d151c3e35c4e06617e0dccfac90507e8b921c53ba06c48d10017b004f
|
4
|
+
data.tar.gz: c216ed5afe37826d7bb25ed75c2ee28d330723e24b5a4fe7f20e8bf4644e9ed3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 336797ea6a5cb43daecbae9d744d4a6fb7b512b412e98495893dcf57f5f865a1172c246bbf57856a4d37613b49793f480700c87aae9bf57da69c431491e182cb
|
7
|
+
data.tar.gz: 4d1332d1c5e49fafdea99126555dcd98d130f0e2eb09ec4c8e1a7dbb0edc6e6a1a2dbb3dcf40d761a14f466d6e144e856c0d1bc14d43403e01f4a664d9a529de
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2014 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'MonsterQueries'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
Bundler::GemHelper.install_tasks
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'lib'
|
28
|
+
t.libs << 'test'
|
29
|
+
t.pattern = 'test/**/*_test.rb'
|
30
|
+
t.verbose = false
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
task default: :test
|
data/config/routes.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module MonsterQueries
|
2
|
+
module ActionController
|
3
|
+
def set_pagination_headers count_json
|
4
|
+
json = JSON.parse(count_json)
|
5
|
+
json = json.first if json.is_a?(Array)
|
6
|
+
page = json['page']
|
7
|
+
total = json['total_entries']
|
8
|
+
per_page = json['per_page'] || 20
|
9
|
+
pages = (total.to_f / per_page).ceil
|
10
|
+
end_entry = page * per_page
|
11
|
+
end_entry = total if end_entry > total
|
12
|
+
headers["X-Pagination"] = {
|
13
|
+
page: page,
|
14
|
+
total: total,
|
15
|
+
total_pages: pages,
|
16
|
+
first_page: page == 1,
|
17
|
+
last_page: page >= pages,
|
18
|
+
previous_page: page - 1,
|
19
|
+
next_page: page + 1,
|
20
|
+
out_of_bounds: page < 1 || page > pages,
|
21
|
+
first_entry: (page - 1) * per_page + 1,
|
22
|
+
end_entry: end_entry
|
23
|
+
}.to_json
|
24
|
+
end
|
25
|
+
|
26
|
+
# AB - We should phase out {{sort}} in favour
|
27
|
+
# of {{order}} {{by}} for more finetune control
|
28
|
+
def render_paginated target, method, attrs
|
29
|
+
if attrs.key?(:sort)
|
30
|
+
name,dir = attrs[:sort].split ','
|
31
|
+
v = [name]
|
32
|
+
v.push dir.upcase if dir
|
33
|
+
v = v.join ' '
|
34
|
+
attrs[:sort] = v
|
35
|
+
attrs[:order] = name
|
36
|
+
attrs[:by] = (dir || 'ASC').upcase
|
37
|
+
end
|
38
|
+
attrs[:count] = true
|
39
|
+
count_json = target.send method,attrs
|
40
|
+
set_pagination_headers count_json
|
41
|
+
attrs.delete(:count)
|
42
|
+
target.send method, attrs
|
43
|
+
end
|
44
|
+
|
45
|
+
def render_paginated_json target, method, attrs
|
46
|
+
json = render_paginated target, method, attrs
|
47
|
+
render json: json
|
48
|
+
end
|
49
|
+
|
50
|
+
def index_params
|
51
|
+
params.permit :page, :search, :sort
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module MonsterQueries
|
2
|
+
module ActiveRecord
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def execute query
|
6
|
+
begin
|
7
|
+
self.class.connection.execute query
|
8
|
+
rescue
|
9
|
+
File.open(Rails.root.join('tmp','failed.sql'), 'w') { |file| file.write(query) }
|
10
|
+
Rails.root.join
|
11
|
+
raise
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def select_value query
|
16
|
+
begin
|
17
|
+
self.class.connection.select_value query
|
18
|
+
rescue
|
19
|
+
File.open(Rails.root.join('tmp','failed.sql'), 'w') { |file| file.write(query) }
|
20
|
+
raise
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def select_values query
|
25
|
+
begin
|
26
|
+
self.class.connection.select_values query
|
27
|
+
rescue
|
28
|
+
File.open(Rails.root.join('tmp','failed.sql'), 'w') { |file| file.write(query) }
|
29
|
+
raise
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def select_json query, count
|
34
|
+
if count
|
35
|
+
select_object query
|
36
|
+
else
|
37
|
+
select_array query
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def select_array query
|
42
|
+
sql = <<-SQL
|
43
|
+
SELECT COALESCE(array_to_json(array_agg(row_to_json(query_row))), '[]'::json)
|
44
|
+
FROM (#{query}) query_row
|
45
|
+
SQL
|
46
|
+
select_value sql
|
47
|
+
end
|
48
|
+
|
49
|
+
def select_object query
|
50
|
+
sql = <<-SQL
|
51
|
+
SELECT COALESCE(row_to_json(query_row),'{}'::json)
|
52
|
+
FROM (#{query}) query_row
|
53
|
+
SQL
|
54
|
+
select_value sql
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def select_all query
|
59
|
+
self.class.connection.select_all query
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
module ClassMethods
|
64
|
+
def execute query
|
65
|
+
connection.execute query
|
66
|
+
end
|
67
|
+
|
68
|
+
def select_value query
|
69
|
+
connection.select_value query
|
70
|
+
end
|
71
|
+
|
72
|
+
def select_values query
|
73
|
+
connection.select_values query
|
74
|
+
end
|
75
|
+
|
76
|
+
def select_json query, count
|
77
|
+
if count
|
78
|
+
select_object query
|
79
|
+
else
|
80
|
+
select_array query
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def select_array query
|
85
|
+
sql = <<-SQL
|
86
|
+
SELECT COALESCE(array_to_json(array_agg(row_to_json(query_row))), '[]'::json)
|
87
|
+
FROM (#{query}) query_row
|
88
|
+
SQL
|
89
|
+
select_value sql
|
90
|
+
end
|
91
|
+
|
92
|
+
def select_object query
|
93
|
+
sql = <<-SQL
|
94
|
+
SELECT COALESCE(row_to_json(query_row),'{}'::json)
|
95
|
+
FROM (#{query}) query_row
|
96
|
+
SQL
|
97
|
+
select_value sql
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end # module
|
101
|
+
end # module
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module MonsterQueries
|
2
|
+
class Builder
|
3
|
+
def self.with_scope args
|
4
|
+
q = self.new nil
|
5
|
+
q.scope = args
|
6
|
+
q
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_writer :scope, :paginate
|
10
|
+
|
11
|
+
def initialize name
|
12
|
+
@paginate = false
|
13
|
+
@scope = [name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing name, args=nil
|
17
|
+
@scope << name
|
18
|
+
file_exists? ? file_contents(args) : self
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s args={}
|
22
|
+
if file_exists?
|
23
|
+
::Rails.logger.tagged('MONSTER QUERY') do
|
24
|
+
::Rails.logger.info {"Rendered #{@scope.join('.')}"}
|
25
|
+
end
|
26
|
+
file_contents args
|
27
|
+
else
|
28
|
+
raise "Query doesn't exist: #{@scope.join('.')}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def paginate
|
33
|
+
@paginate = true
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def file_exists?
|
40
|
+
Q.exists? @scope
|
41
|
+
end
|
42
|
+
|
43
|
+
def file_contents variables
|
44
|
+
template = MonsterQueries::Query.template @scope
|
45
|
+
result = template.call variables
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end # class
|
49
|
+
end # module
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'handlebars'
|
3
|
+
require 'pry'
|
4
|
+
module MonsterQueries
|
5
|
+
class Query
|
6
|
+
@template_cache = {}
|
7
|
+
@exists_cache = {}
|
8
|
+
SKIP_CACHE = true
|
9
|
+
|
10
|
+
def self.template_from_parts parts, vars
|
11
|
+
MonsterQueries::Builder.with_scope(parts).to_s vars
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.template_from_string string, vars
|
15
|
+
template = handlebars.compile string, noEscape: true
|
16
|
+
result = template.call vars
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
# Method Missing is used to create a chain path
|
21
|
+
# to the query eg. Q.admin.users.index
|
22
|
+
def self.method_missing name
|
23
|
+
MonsterQueries::Builder.new name
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.paginate
|
27
|
+
q = MonsterQueries::Builder.new nil
|
28
|
+
q.paginate = true
|
29
|
+
q
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.locate_file scope
|
33
|
+
search_paths = [Rails.root,MonsterQueries::Engine.root]
|
34
|
+
search_paths.each do |path|
|
35
|
+
file = path.join('app','queries',*scope.compact.map(&:to_s)).to_s + '.sql'
|
36
|
+
return file if File.exists?(file)
|
37
|
+
end
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.template scope
|
42
|
+
if !@template_cache[scope.join('.')] || SKIP_CACHE
|
43
|
+
file_name = locate_file(scope)
|
44
|
+
data = File.read file_name
|
45
|
+
@template_cache[scope.join('.')] = handlebars.compile(data, noEscape: true)
|
46
|
+
end
|
47
|
+
@template_cache[scope.join('.')]
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.exists? scope
|
51
|
+
if !@exists_cache[scope] || SKIP_CACHE
|
52
|
+
file_name = locate_file(scope)
|
53
|
+
@exists_cache[scope] = file_name && File.file?(file_name)
|
54
|
+
end
|
55
|
+
@exists_cache[scope]
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.handlebars
|
59
|
+
return @handlebars if @handlebars
|
60
|
+
@handlebars = Handlebars::Context.new
|
61
|
+
@handlebars.register_helper :include , &(method(:helper_include).to_proc)
|
62
|
+
@handlebars.register_helper :paginate , &(method(:helper_paginate).to_proc)
|
63
|
+
@handlebars.register_helper :paginate_offset, &(method(:helper_paginate_offset).to_proc)
|
64
|
+
@handlebars.register_helper :wildcard , &(method(:helper_wildcard).to_proc)
|
65
|
+
@handlebars.register_helper :quote , &(method(:helper_quote).to_proc)
|
66
|
+
@handlebars.register_helper :int , &(method(:helper_int).to_proc)
|
67
|
+
@handlebars.register_helper :float , &(method(:helper_float).to_proc)
|
68
|
+
@handlebars.register_helper :array , &(method(:helper_array).to_proc)
|
69
|
+
@handlebars.register_helper :object , &(method(:helper_object).to_proc)
|
70
|
+
@handlebars
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.helper_include context, name, options
|
74
|
+
vars = {}
|
75
|
+
context.each do |k,v|
|
76
|
+
vars[k] = v
|
77
|
+
end
|
78
|
+
options['hash'].each do |k,v|
|
79
|
+
vars[k] = v
|
80
|
+
end if options
|
81
|
+
parts = name.split('.')
|
82
|
+
MonsterQueries::Builder.with_scope(parts).to_s vars
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.helper_paginate context, value, options
|
86
|
+
if value.is_a?(String)
|
87
|
+
count = !!context["count"]
|
88
|
+
name = count ? 'pagination.select' : value
|
89
|
+
self.helper_include context, name, options
|
90
|
+
else
|
91
|
+
value.fn context
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.helper_paginate_offset context, value, options
|
96
|
+
self.helper_include context, 'pagination.offset', options
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.helper_wildcard context, value, options
|
100
|
+
::ActiveRecord::Base.connection.quote "%#{value.gsub('\\','\\\\\\')}%"
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.helper_quote context, value, options
|
104
|
+
if value.is_a?(V8::Array)
|
105
|
+
value.collect{|v| ::ActiveRecord::Base.connection.quote v}.join(',')
|
106
|
+
else
|
107
|
+
::ActiveRecord::Base.connection.quote value
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.helper_int context, value, options
|
112
|
+
value.to_i
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.helper_float context, value, options
|
116
|
+
value.to_f
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.helper_array context, block, options
|
120
|
+
content =
|
121
|
+
if block.is_a?(String)
|
122
|
+
"\n" + helper_include(context, block, options)
|
123
|
+
else
|
124
|
+
block.fn context
|
125
|
+
end
|
126
|
+
<<-HEREDOC
|
127
|
+
(SELECT COALESCE(array_to_json(array_agg(row_to_json(array_row))),'[]'::json) FROM (
|
128
|
+
#{content}
|
129
|
+
) array_row)
|
130
|
+
HEREDOC
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.helper_object context, block, options
|
134
|
+
content =
|
135
|
+
if block.is_a?(String)
|
136
|
+
"\n" + helper_include(context, block, options)
|
137
|
+
else
|
138
|
+
block.fn context
|
139
|
+
end
|
140
|
+
<<-HEREDOC
|
141
|
+
(SELECT COALESCE(row_to_json(object_row),'{}'::json) FROM (
|
142
|
+
#{content}
|
143
|
+
) object_row)
|
144
|
+
HEREDOC
|
145
|
+
end
|
146
|
+
end # class
|
147
|
+
end # module
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: monster-queries
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Monsterbox Productions
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-11-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.2.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.2.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: handlebars
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: libv8
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Queries
|
70
|
+
email:
|
71
|
+
- andrew@monsterboxpro.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- MIT-LICENSE
|
77
|
+
- Rakefile
|
78
|
+
- app/queries/pagination/offset.sql
|
79
|
+
- app/queries/pagination/select.sql
|
80
|
+
- config/initializers/query.rb
|
81
|
+
- config/routes.rb
|
82
|
+
- lib/monster-queries.rb
|
83
|
+
- lib/monster_queries/action_controller.rb
|
84
|
+
- lib/monster_queries/active_record.rb
|
85
|
+
- lib/monster_queries/builder.rb
|
86
|
+
- lib/monster_queries/engine.rb
|
87
|
+
- lib/monster_queries/helpers.rb
|
88
|
+
- lib/monster_queries/query.rb
|
89
|
+
- lib/monster_queries/version.rb
|
90
|
+
- lib/tasks/monster_queries_tasks.rake
|
91
|
+
homepage: http://monsterboxpro.com
|
92
|
+
licenses:
|
93
|
+
- MIT
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 2.7.8
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: Queries
|
115
|
+
test_files: []
|