graphql 1.5.15 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +4 -19
  3. data/lib/graphql/analysis/analyze_query.rb +27 -2
  4. data/lib/graphql/analysis/query_complexity.rb +10 -11
  5. data/lib/graphql/argument.rb +7 -6
  6. data/lib/graphql/backwards_compatibility.rb +47 -0
  7. data/lib/graphql/compatibility/execution_specification.rb +14 -0
  8. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +6 -1
  9. data/lib/graphql/compatibility/lazy_execution_specification.rb +19 -0
  10. data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +15 -6
  11. data/lib/graphql/directive.rb +1 -6
  12. data/lib/graphql/execution.rb +1 -0
  13. data/lib/graphql/execution/execute.rb +174 -160
  14. data/lib/graphql/execution/field_result.rb +5 -1
  15. data/lib/graphql/execution/lazy.rb +2 -2
  16. data/lib/graphql/execution/lazy/resolve.rb +8 -11
  17. data/lib/graphql/execution/multiplex.rb +134 -0
  18. data/lib/graphql/execution/selection_result.rb +5 -0
  19. data/lib/graphql/field.rb +1 -8
  20. data/lib/graphql/filter.rb +53 -0
  21. data/lib/graphql/internal_representation/node.rb +11 -6
  22. data/lib/graphql/internal_representation/rewrite.rb +3 -3
  23. data/lib/graphql/query.rb +160 -78
  24. data/lib/graphql/query/arguments.rb +14 -25
  25. data/lib/graphql/query/arguments_cache.rb +6 -13
  26. data/lib/graphql/query/context.rb +28 -10
  27. data/lib/graphql/query/executor.rb +1 -0
  28. data/lib/graphql/query/literal_input.rb +10 -4
  29. data/lib/graphql/query/null_context.rb +1 -1
  30. data/lib/graphql/query/serial_execution/field_resolution.rb +5 -1
  31. data/lib/graphql/query/validation_pipeline.rb +12 -7
  32. data/lib/graphql/query/variables.rb +1 -1
  33. data/lib/graphql/rake_task.rb +140 -0
  34. data/lib/graphql/relay/array_connection.rb +29 -48
  35. data/lib/graphql/relay/base_connection.rb +9 -7
  36. data/lib/graphql/relay/mutation.rb +0 -11
  37. data/lib/graphql/relay/mutation/instrumentation.rb +2 -2
  38. data/lib/graphql/relay/mutation/resolve.rb +7 -10
  39. data/lib/graphql/relay/relation_connection.rb +98 -61
  40. data/lib/graphql/scalar_type.rb +1 -15
  41. data/lib/graphql/schema.rb +90 -25
  42. data/lib/graphql/schema/build_from_definition.rb +22 -23
  43. data/lib/graphql/schema/build_from_definition/resolve_map.rb +70 -0
  44. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +47 -0
  45. data/lib/graphql/schema/middleware_chain.rb +1 -1
  46. data/lib/graphql/schema/printer.rb +2 -1
  47. data/lib/graphql/schema/timeout_middleware.rb +6 -6
  48. data/lib/graphql/schema/type_map.rb +1 -1
  49. data/lib/graphql/schema/warden.rb +5 -9
  50. data/lib/graphql/static_validation/definition_dependencies.rb +1 -1
  51. data/lib/graphql/version.rb +1 -1
  52. data/spec/graphql/analysis/analyze_query_spec.rb +2 -2
  53. data/spec/graphql/analysis/max_query_complexity_spec.rb +28 -0
  54. data/spec/graphql/argument_spec.rb +3 -3
  55. data/spec/graphql/execution/lazy_spec.rb +8 -114
  56. data/spec/graphql/execution/multiplex_spec.rb +131 -0
  57. data/spec/graphql/internal_representation/rewrite_spec.rb +10 -0
  58. data/spec/graphql/query/arguments_spec.rb +14 -16
  59. data/spec/graphql/query/context_spec.rb +14 -1
  60. data/spec/graphql/query/literal_input_spec.rb +19 -13
  61. data/spec/graphql/query/variables_spec.rb +1 -1
  62. data/spec/graphql/query_spec.rb +12 -1
  63. data/spec/graphql/rake_task_spec.rb +57 -0
  64. data/spec/graphql/relay/array_connection_spec.rb +24 -3
  65. data/spec/graphql/relay/connection_instrumentation_spec.rb +23 -0
  66. data/spec/graphql/relay/mutation_spec.rb +2 -10
  67. data/spec/graphql/relay/page_info_spec.rb +2 -2
  68. data/spec/graphql/relay/relation_connection_spec.rb +167 -3
  69. data/spec/graphql/schema/build_from_definition_spec.rb +93 -19
  70. data/spec/graphql/schema/warden_spec.rb +80 -0
  71. data/spec/graphql/schema_spec.rb +26 -2
  72. data/spec/spec_helper.rb +4 -2
  73. data/spec/support/lazy_helpers.rb +152 -0
  74. data/spec/support/star_wars/schema.rb +23 -0
  75. metadata +28 -3
  76. data/lib/graphql/schema/mask.rb +0 -55
@@ -5,10 +5,9 @@ module GraphQL
5
5
  #
6
6
  # {Arguments} recursively wraps the input in {Arguments} instances.
7
7
  class Arguments
8
- extend GraphQL::Delegate
8
+ extend Forwardable
9
9
 
10
10
  def initialize(values, argument_definitions:)
11
- @original_values = values
12
11
  @argument_values = values.inject({}) do |memo, (inner_key, inner_value)|
13
12
  arg_defn = argument_definitions[inner_key.to_s]
14
13
 
@@ -22,24 +21,29 @@ module GraphQL
22
21
  # @param key [String, Symbol] name or index of value to access
23
22
  # @return [Object] the argument at that key
24
23
  def [](key)
25
- key_s = key.is_a?(String) ? key : key.to_s
26
- @argument_values.fetch(key_s, NULL_ARGUMENT_VALUE).value
24
+ @argument_values.fetch(key.to_s, NULL_ARGUMENT_VALUE).value
27
25
  end
28
26
 
29
27
  # @param key [String, Symbol] name of value to access
30
28
  # @return [Boolean] true if the argument was present in this field
31
29
  def key?(key)
32
- key_s = key.is_a?(String) ? key : key.to_s
33
- @argument_values.key?(key_s)
30
+ @argument_values.key?(key.to_s)
34
31
  end
35
32
 
36
- # Get the original Ruby hash
37
- # @return [Hash] the original values hash
33
+ # Get the hash of all values, with stringified keys
34
+ # @return [Hash] the stringified hash
38
35
  def to_h
39
- @unwrapped_values ||= unwrap_value(@original_values)
36
+ @to_h ||= begin
37
+ h = {}
38
+ each_value do |arg_value|
39
+ arg_key = arg_value.definition.expose_as
40
+ h[arg_key] = unwrap_value(arg_value.value)
41
+ end
42
+ h
43
+ end
40
44
  end
41
45
 
42
- def_delegators :string_key_values, :keys, :values, :each
46
+ def_delegators :to_h, :keys, :values, :each
43
47
 
44
48
  # Access each key, value and type for the arguments in this set.
45
49
  # @yield [argument_value] The {ArgumentValue} for each argument
@@ -101,21 +105,6 @@ module GraphQL
101
105
  value
102
106
  end
103
107
  end
104
-
105
- def string_key_values
106
- @string_key_values ||= stringify_keys(to_h)
107
- end
108
-
109
- def stringify_keys(value)
110
- case value
111
- when Hash
112
- value.inject({}) { |memo, (k, v)| memo[k.to_s] = stringify_keys(v); memo }
113
- when Array
114
- value.map { |v| stringify_keys(v) }
115
- else
116
- value
117
- end
118
- end
119
108
  end
120
109
  end
121
110
  end
@@ -5,21 +5,14 @@ module GraphQL
5
5
  # @return [Hash<InternalRepresentation::Node, GraphQL::Language::NodesDirectiveNode => Hash<GraphQL::Field, GraphQL::Directive => GraphQL::Query::Arguments>>]
6
6
  def self.build(query)
7
7
  Hash.new do |h1, irep_or_ast_node|
8
- h1[irep_or_ast_node] = Hash.new do |h2, definition|
8
+ Hash.new do |h2, definition|
9
9
  ast_node = irep_or_ast_node.is_a?(GraphQL::InternalRepresentation::Node) ? irep_or_ast_node.ast_node : irep_or_ast_node
10
10
  ast_arguments = ast_node.arguments
11
-
12
- h2[definition] = if ast_arguments.none?
13
- definition.default_arguments
14
- elsif definition.arguments.none?
15
- GraphQL::Query::Arguments::NO_ARGS
16
- else
17
- GraphQL::Query::LiteralInput.from_arguments(
18
- ast_arguments,
19
- definition.arguments,
20
- query.variables,
21
- )
22
- end
11
+ GraphQL::Query::LiteralInput.from_arguments(
12
+ ast_arguments,
13
+ definition.arguments,
14
+ query.variables,
15
+ )
23
16
  end
24
17
  end
25
18
  end
@@ -4,6 +4,7 @@ module GraphQL
4
4
  # Expose some query-specific info to field resolve functions.
5
5
  # It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
6
6
  class Context
7
+ extend Forwardable
7
8
  attr_reader :execution_strategy
8
9
  # `strategy` is required by GraphQL::Batch
9
10
  alias_method :strategy, :execution_strategy
@@ -41,24 +42,32 @@ module GraphQL
41
42
  def initialize(query:, values:)
42
43
  @query = query
43
44
  @schema = query.schema
44
- @values = values || {}
45
+ @provided_values = values || {}
46
+ # Namespaced storage, where user-provided values are in `nil` namespace:
47
+ @storage = Hash.new { |h, k| h[k] = {} }
48
+ @storage[nil] = @provided_values
45
49
  @errors = []
46
50
  @path = []
47
51
  end
48
52
 
49
- # Lookup `key` from the hash passed to {Schema#execute} as `context:`
50
- def [](key)
51
- @values[key]
52
- end
53
+ def_delegators :@provided_values, :[], :[]=, :to_h, :key?, :fetch
54
+
55
+ # @!method [](key)
56
+ # Lookup `key` from the hash passed to {Schema#execute} as `context:`
57
+
58
+ # @!method []=(key, value)
59
+ # Reassign `key` to the hash passed to {Schema#execute} as `context:`
53
60
 
54
61
  # @return [GraphQL::Schema::Warden]
55
62
  def warden
56
63
  @warden ||= @query.warden
57
64
  end
58
65
 
59
- # Reassign `key` to the hash passed to {Schema#execute} as `context:`
60
- def []=(key, value)
61
- @values[key] = value
66
+ # Get an isolated hash for `ns`. Doesn't affect user-provided storage.
67
+ # @param ns [Object] a usage-specific namespace identifier
68
+ # @return [Hash] namespaced storage
69
+ def namespace(ns)
70
+ @storage[ns]
62
71
  end
63
72
 
64
73
  def spawn(key:, selection:, parent_type:, field:)
@@ -71,8 +80,14 @@ module GraphQL
71
80
  )
72
81
  end
73
82
 
83
+ # Return this value to tell the runtime
84
+ # to exclude this field from the response altogether
85
+ def skip
86
+ GraphQL::Execution::Execute::SKIP
87
+ end
88
+
74
89
  class FieldResolutionContext
75
- extend GraphQL::Delegate
90
+ extend Forwardable
76
91
 
77
92
  attr_reader :path, :selection, :field, :parent_type
78
93
 
@@ -84,7 +99,10 @@ module GraphQL
84
99
  @parent_type = parent_type
85
100
  end
86
101
 
87
- def_delegators :@context, :[], :[]=, :spawn, :query, :schema, :warden, :errors, :execution_strategy, :strategy
102
+ def_delegators :@context,
103
+ :[], :[]=, :key?, :fetch, :to_h, :namespace,
104
+ :spawn, :query, :schema, :warden, :errors,
105
+ :execution_strategy, :strategy, :skip
88
106
 
89
107
  # @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field
90
108
  def ast_node
@@ -8,6 +8,7 @@ module GraphQL
8
8
  attr_reader :query
9
9
 
10
10
  def initialize(query)
11
+ warn("Executor is deprecated; use Schema#execute")
11
12
  @query = query
12
13
  end
13
14
 
@@ -32,7 +32,7 @@ module GraphQL
32
32
  end
33
33
 
34
34
  def self.defaults_for(argument_defns)
35
- if argument_defns.none? { |name, arg| arg.default_value? }
35
+ if argument_defns.values.none?(&:default_value?)
36
36
  GraphQL::Query::Arguments::NO_ARGS
37
37
  else
38
38
  from_arguments([], argument_defns, nil)
@@ -40,7 +40,8 @@ module GraphQL
40
40
  end
41
41
 
42
42
  def self.from_arguments(ast_arguments, argument_defns, variables)
43
-
43
+ # Variables is nil when making .defaults_for
44
+ context = variables ? variables.context : nil
44
45
  values_hash = {}
45
46
  indexed_arguments = ast_arguments.each_with_object({}) { |a, memo| memo[a.name] = a }
46
47
 
@@ -56,7 +57,7 @@ module GraphQL
56
57
  if (!value_is_a_variable || (value_is_a_variable && variables.key?(ast_arg.value.name)))
57
58
 
58
59
  value = coerce(arg_defn.type, ast_arg.value, variables)
59
- value = arg_defn.prepare(value)
60
+ value = arg_defn.prepare(value, context)
60
61
 
61
62
  if value.is_a?(GraphQL::ExecutionError)
62
63
  value.ast_node = ast_arg
@@ -72,7 +73,12 @@ module GraphQL
72
73
  # a value wasn't provided from the AST,
73
74
  # then add the default value.
74
75
  if arg_defn.default_value? && !values_hash.key?(arg_name)
75
- values_hash[arg_name] = arg_defn.default_value
76
+ value = arg_defn.default_value
77
+ # `context` isn't present when pre-calculating defaults
78
+ if context
79
+ value = arg_defn.prepare(value, context)
80
+ end
81
+ values_hash[arg_name] = value
76
82
  end
77
83
  end
78
84
 
@@ -9,7 +9,7 @@ module GraphQL
9
9
  @query = nil
10
10
  @schema = GraphQL::Schema.new
11
11
  @warden = GraphQL::Schema::Warden.new(
12
- GraphQL::Schema::NullMask,
12
+ GraphQL::Filter.new,
13
13
  context: self,
14
14
  schema: @schema,
15
15
  )
@@ -24,7 +24,11 @@ module GraphQL
24
24
  def result
25
25
  result_name = irep_node.name
26
26
  raw_value = get_raw_value
27
- { result_name => get_finished_value(raw_value) }
27
+ if raw_value == GraphQL::Execution::Execute::SKIP
28
+ {}
29
+ else
30
+ { result_name => get_finished_value(raw_value) }
31
+ end
28
32
  end
29
33
 
30
34
  # GraphQL::Batch depends on this
@@ -52,6 +52,11 @@ module GraphQL
52
52
  @internal_representation
53
53
  end
54
54
 
55
+ def analyzers
56
+ ensure_has_validated
57
+ @query_analyzers
58
+ end
59
+
55
60
  private
56
61
 
57
62
  # If the pipeline wasn't run yet, run it.
@@ -80,13 +85,13 @@ module GraphQL
80
85
  end
81
86
 
82
87
  if @validation_errors.none?
83
- query_analyzers = build_analyzers(@schema, @max_depth, @max_complexity)
84
- if query_analyzers.any?
85
- analysis_results = GraphQL::Analysis.analyze_query(@query, query_analyzers)
86
- @analysis_errors = analysis_results
87
- .flatten # accept n-dimensional array
88
- .select { |r| r.is_a?(GraphQL::AnalysisError) }
89
- end
88
+ @query_analyzers = build_analyzers(@schema, @max_depth, @max_complexity)
89
+ # if query_analyzers.any?
90
+ # analysis_results = GraphQL::Analysis.analyze_query(@query, query_analyzers)
91
+ # @analysis_errors = analysis_results
92
+ # .flatten # accept n-dimensional array
93
+ # .select { |r| r.is_a?(GraphQL::AnalysisError) }
94
+ # end
90
95
  end
91
96
  end
92
97
 
@@ -3,7 +3,7 @@ module GraphQL
3
3
  class Query
4
4
  # Read-only access to query variables, applying default values if needed.
5
5
  class Variables
6
- extend GraphQL::Delegate
6
+ extend Forwardable
7
7
 
8
8
  # @return [Array<GraphQL::Query::VariableValidationError>] Any errors encountered when parsing the provided variables and literal values
9
9
  attr_reader :errors
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+ require "fileutils"
3
+ module GraphQL
4
+ # A rake task for dumping a schema as IDL or JSON.
5
+ #
6
+ # By default, schemas are looked up by name as constants using `schema_name:`.
7
+ # You can provide a `load_schema` function to return your schema another way.
8
+ #
9
+ # `load_context:`, `only:` and `except:` are supported so that
10
+ # you can keep an eye on how filters affect your schema.
11
+ #
12
+ # @example Dump a Schema to .graphql + .json files
13
+ # require "graphql/rake_task"
14
+ # GraphQL::RakeTask.new(schema_name: "MySchema")
15
+ #
16
+ # # $ rake graphql:schema:dump
17
+ # # Schema IDL dumped to ./schema.graphql
18
+ # # Schema JSON dumped to ./schema.json
19
+ #
20
+ # @example Invoking the task from Ruby
21
+ # require "rake"
22
+ # Rake::Task["graphql:schema:dump"].invoke
23
+ class RakeTask
24
+ include Rake::DSL
25
+
26
+ DEFAULT_OPTIONS = {
27
+ namespace: "graphql",
28
+ dependencies: nil,
29
+ schema_name: nil,
30
+ load_schema: ->(task) { Object.const_get(task.schema_name) },
31
+ load_context: ->(task) { {} },
32
+ only: nil,
33
+ except: nil,
34
+ directory: ".",
35
+ idl_outfile: "schema.graphql",
36
+ json_outfile: "schema.json",
37
+ }
38
+
39
+ # @return [String] Namespace for generated tasks
40
+ attr_writer :namespace
41
+
42
+ def rake_namespace
43
+ @namespace
44
+ end
45
+
46
+ # @return [Array<String>]
47
+ attr_accessor :dependencies
48
+
49
+ # @return [String] By default, used to find the schema as a constant.
50
+ # @see {#load_schema} for loading a schema another way
51
+ attr_accessor :schema_name
52
+
53
+ # @return [<#call(task)>] A proc for loading the target GraphQL schema
54
+ attr_accessor :load_schema
55
+
56
+ # @return [<#call(task)>] A callable for loading the query context
57
+ attr_accessor :load_context
58
+
59
+ # @return [<#call(member, ctx)>, nil] A filter for this task
60
+ attr_accessor :only
61
+
62
+ # @return [<#call(member, ctx)>, nil] A filter for this task
63
+ attr_accessor :except
64
+
65
+ # @return [String] target for IDL task
66
+ attr_accessor :idl_outfile
67
+
68
+ # @return [String] target for JSON task
69
+ attr_accessor :json_outfile
70
+
71
+ # @return [String] directory for IDL & JSON files
72
+ attr_accessor :directory
73
+
74
+ # Set the parameters of this task by passing keyword arguments
75
+ # or assigning attributes inside the block
76
+ def initialize(options = {})
77
+ default_dependencies = if Rake::Task.task_defined?("environment")
78
+ [:environment]
79
+ else
80
+ []
81
+ end
82
+
83
+ all_options = DEFAULT_OPTIONS
84
+ .merge(dependencies: default_dependencies)
85
+ .merge(options)
86
+ all_options.each do |k, v|
87
+ self.public_send("#{k}=", v)
88
+ end
89
+
90
+ if block_given?
91
+ yield(self)
92
+ end
93
+
94
+ define_task
95
+ end
96
+
97
+ private
98
+
99
+ # Use the provided `method_name` to generate a string from the specified schema
100
+ # then write it to `file`.
101
+ def write_outfile(method_name, file)
102
+ schema = @load_schema.call(self)
103
+ context = @load_context.call(self)
104
+ result = schema.public_send(method_name, only: @only, except: @except, context: context)
105
+ dir = File.dirname(file)
106
+ FileUtils.mkdir_p(dir)
107
+ File.write(file, result)
108
+ end
109
+
110
+ def idl_path
111
+ File.join(@directory, @idl_outfile)
112
+ end
113
+
114
+ def json_path
115
+ File.join(@directory, @json_outfile)
116
+ end
117
+
118
+ # Use the Rake DSL to add tasks
119
+ def define_task
120
+ namespace(@namespace) do
121
+ namespace("schema") do
122
+ desc("Dump the schema to IDL in #{idl_path}")
123
+ task :idl => @dependencies do
124
+ write_outfile(:to_definition, idl_path)
125
+ puts "Schema IDL dumped into #{idl_path}"
126
+ end
127
+
128
+ desc("Dump the schema to JSON in #{json_path}")
129
+ task :json => @dependencies do
130
+ write_outfile(:to_json, json_path)
131
+ puts "Schema JSON dumped into #{json_path}"
132
+ end
133
+
134
+ desc("Dump the schema to JSON and IDL")
135
+ task :dump => [:idl, :json]
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -3,78 +3,59 @@ module GraphQL
3
3
  module Relay
4
4
  class ArrayConnection < BaseConnection
5
5
  def cursor_from_node(item)
6
- idx = starting_offset + sliced_nodes.find_index(item) + 1
6
+ idx = (after ? index_from_cursor(after) : 0) + sliced_nodes.find_index(item) + 1
7
7
  encode(idx.to_s)
8
8
  end
9
9
 
10
- def has_next_page
11
- !!(first && sliced_nodes.count > limit)
12
- end
10
+ private
11
+
12
+ def first
13
+ return @first if defined? @first
13
14
 
14
- def has_previous_page
15
- !!(last && starting_offset > 0)
15
+ @first = get_limited_arg(:first)
16
+ @first = max_page_size if @first && max_page_size && @first > max_page_size
17
+ @first
16
18
  end
17
19
 
18
- private
20
+ def last
21
+ return @last if defined? @last
22
+
23
+ @last = get_limited_arg(:last)
24
+ @last = max_page_size if @last && max_page_size && @last > max_page_size
25
+ @last
26
+ end
19
27
 
20
28
  # apply first / last limit results
21
29
  def paged_nodes
22
30
  @paged_nodes ||= begin
23
31
  items = sliced_nodes
24
32
 
25
- if limit
26
- items.first(limit)
27
- else
28
- items
29
- end
33
+ items = items.first(first) if first
34
+ items = items.last(last) if last
35
+ items = items.first(max_page_size) if max_page_size && !first && !last
36
+
37
+ items
30
38
  end
31
39
  end
32
40
 
33
41
  # Apply cursors to edges
34
42
  def sliced_nodes
35
- @sliced_nodes ||= nodes[starting_offset..-1] || []
36
- end
37
-
38
- def index_from_cursor(cursor)
39
- decode(cursor).to_i
40
- end
41
-
42
- def starting_offset
43
- @starting_offset = if before
44
- [previous_offset, 0].max
45
- elsif last
46
- [nodes.count - last, 0].max
47
- else
48
- previous_offset
49
- end
50
- end
51
-
52
- def previous_offset
53
- @previous_offset ||= if after
54
- index_from_cursor(after)
43
+ @sliced_nodes ||= if before && after
44
+ nodes[index_from_cursor(after)..index_from_cursor(before)-1] || []
55
45
  elsif before
56
- prev_page_size = [max_page_size, last].compact.min || 0
57
- index_from_cursor(before) - prev_page_size - 1
46
+ nodes[0..index_from_cursor(before)-2] || []
47
+ elsif after
48
+ nodes[index_from_cursor(after)..-1] || []
58
49
  else
59
- 0
50
+ nodes
60
51
  end
61
52
  end
62
53
 
63
- def limit
64
- @limit ||= begin
65
- limit_from_arguments = if first
66
- first
67
- else
68
- if previous_offset < 0
69
- previous_offset + (last ? last : 0)
70
- else
71
- last
72
- end
73
- end
74
- [limit_from_arguments, max_page_size].compact.min
75
- end
54
+ def index_from_cursor(cursor)
55
+ decode(cursor).to_i
76
56
  end
77
57
  end
58
+
78
59
  BaseConnection.register_connection_implementation(Array, ArrayConnection)
79
60
  end
80
61
  end