Class: ElasticGraph::QueryRegistry::ClientData

Inherits:
Data
  • Object
show all
Defined in:
lib/elastic_graph/query_registry/client_data.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#canonical_query_stringsObject (readonly)

Returns the value of attribute canonical_query_strings

Returns:

  • (Object)

    the current value of canonical_query_strings



11
12
13
# File 'lib/elastic_graph/query_registry/client_data.rb', line 11

def canonical_query_strings
  @canonical_query_strings
end

#operation_namesObject (readonly)

Returns the value of attribute operation_names

Returns:

  • (Object)

    the current value of operation_names



11
12
13
# File 'lib/elastic_graph/query_registry/client_data.rb', line 11

def operation_names
  @operation_names
end

#queries_by_last_stringObject (readonly)

Returns the value of attribute queries_by_last_string

Returns:

  • (Object)

    the current value of queries_by_last_string



11
12
13
# File 'lib/elastic_graph/query_registry/client_data.rb', line 11

def queries_by_last_string
  @queries_by_last_string
end

#queries_by_original_stringObject (readonly)

Returns the value of attribute queries_by_original_string

Returns:

  • (Object)

    the current value of queries_by_original_string



11
12
13
# File 'lib/elastic_graph/query_registry/client_data.rb', line 11

def queries_by_original_string
  @queries_by_original_string
end

#schema_element_namesObject (readonly)

Returns the value of attribute schema_element_names

Returns:

  • (Object)

    the current value of schema_element_names



11
12
13
# File 'lib/elastic_graph/query_registry/client_data.rb', line 11

def schema_element_names
  @schema_element_names
end

Class Method Details

.canonical_query_string_from(query, schema_element_names:) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/elastic_graph/query_registry/client_data.rb', line 84

def self.canonical_query_string_from(query, schema_element_names:)
  return "" unless (document = query.document)

  canonicalized_definitions = document.definitions.map do |definition|
    if definition.directives.empty?
      definition
    else
      # Ignore the `@egLatencySlo` directive if it is present. We want to allow it to be included (or not)
      # and potentially have different values from the registered query so that clients don't have to register
      # a new version of their query just to change the latency SLO value.
      #
      # Note: we don't ignore _all_ directives here because other directives might cause significant behavioral
      # changes that should be enforced by the registry query approval process.
      directives = definition.directives.reject do |dir|
        dir.name == schema_element_names.eg_latency_slo
      end

      definition.merge(directives: directives)
    end
  end

  document.merge(definitions: canonicalized_definitions).to_query_string
end

.from(schema, registered_query_strings) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/elastic_graph/query_registry/client_data.rb', line 13

def self.from(schema, registered_query_strings)
  queries_by_original_string = registered_query_strings.to_h do |query_string|
    [query_string, schema.new_graphql_query(query_string, validate: false)]
  end

  canonical_query_strings = queries_by_original_string.values.map do |q|
    canonical_query_string_from(q, schema_element_names: schema.element_names)
  end.to_set

  operation_names = queries_by_original_string.values.flat_map { |q| q.operations.keys }.to_set

  new(
    queries_by_original_string: queries_by_original_string,
    queries_by_last_string: {},
    canonical_query_strings: canonical_query_strings,
    operation_names: operation_names,
    schema_element_names: schema.element_names
  )
end

Instance Method Details

#cached_query_for(query_string) ⇒ Object



33
34
35
# File 'lib/elastic_graph/query_registry/client_data.rb', line 33

def cached_query_for(query_string)
  queries_by_original_string[query_string] || queries_by_last_string[query_string]
end

#unregistered_query_error_for(query, client) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/elastic_graph/query_registry/client_data.rb', line 56

def unregistered_query_error_for(query, client)
  # Note: we use `selected_operation_name` instead of `operation_name` because `operation_name` can return
  # `nil` for single-operation queries when no explicit operation_name parameter is passed if accessed before
  # the query AST is parsed, whereas `selected_operation_name`  parses the query AST and returns the operation
  # name from the query document in that case.
  selected_op_name = query.selected_operation_name.to_s

  if operation_names.include?(selected_op_name)
    "Query #{fingerprint_for(query)} differs from the registered form of `#{selected_op_name}` " \
    "for client #{client.description}."
  else
    "Query #{fingerprint_for(query)} is unregistered; client #{client.description} has no " \
    "registered query with a `#{selected_op_name}` operation."
  end
end

#with_updated_last_query(query_string, query) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/elastic_graph/query_registry/client_data.rb', line 37

def with_updated_last_query(query_string, query)
  canonical_string = canonical_query_string_from(query)

  # We normally expect to only see one alternate query form from a client. However, a misbehaving
  # client could send us a slightly different query string on each request (imagine if the query
  # had a dynamically generated comment with a timestamp). Here we guard against that case by
  # pruning out the previous hash entry that resolves to the same registered query, ensuring
  # we only cache the most recently seen query string. Note that this operation is unfortunately
  # O(N) instead of O(1) but we expect this operation to happen rarely (and we don't expect many
  # entries in the `queries_by_last_string` hash). We could maintain a 2nd parallel data structure
  # allowing an `O(1)` lookup here but I'd rather not introduce that added complexity for marginal
  # benefit.
  updated_queries_by_last_string = queries_by_last_string.reject do |_, cached_query|
    canonical_query_string_from(cached_query) == canonical_string
  end.merge(query_string => query)

  with(queries_by_last_string: updated_queries_by_last_string)
end