Class: Janeway::Query

Inherits:
Object
  • Object
show all
Defined in:
lib/janeway/query.rb

Overview

Query holds the abstract syntax tree created by parsing the query. This can be frozen and passed to multiple threads or ractors for simultaneous use. No instance members are modified during the interpretation stage.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root_node, jsonpath) ⇒ Query

Returns a new instance of Query.

Parameters:

  • root_node (AST::Root)
  • jsonpath (String)

Raises:

  • (ArgumentError)


17
18
19
20
21
22
23
# File 'lib/janeway/query.rb', line 17

def initialize(root_node, jsonpath)
  raise ArgumentError, "expect root identifier, got #{root_node.inspect}" unless root_node.is_a?(AST::RootNode)
  raise ArgumentError, "expect query string, got #{jsonpath.inspect}" unless jsonpath.is_a?(String)

  @root = root_node
  @jsonpath = jsonpath
end

Instance Attribute Details

#jsonpathString (readonly)

The original jsonpath query, for use in error messages

Returns:

  • (String)


10
11
12
# File 'lib/janeway/query.rb', line 10

def jsonpath
  @jsonpath
end

#rootAST::Root (readonly)

Returns:

  • (AST::Root)


13
14
15
# File 'lib/janeway/query.rb', line 13

def root
  @root
end

Instance Method Details

#==(other) ⇒ Object

Queries are considered equal if their ASTs evaluate to the same JSONPath string.

The string output is generated from the AST and should be considered a "normalized" form of the query. It may have different whitespace and parentheses than the original input but will be semantically equivalent.



87
88
89
# File 'lib/janeway/query.rb', line 87

def ==(other)
  to_s == other.to_s
end

#dupQuery

Deep copy the query.

Only the top-level linked list of segments/selectors is cloned — that is the only part pop mutates. Nested contents (filter expressions, function parameters, child-segment members) are shared with the original, which is safe because the interpreter never mutates the AST.

The tree cache is intentionally NOT shared with the dup: cached trees reference the original's top-level chain and are invalid for a dup that may later be popped.

Returns:



111
112
113
114
115
116
# File 'lib/janeway/query.rb', line 111

def dup
  cloned = node_list.map(&:dup)
  cloned.each_cons(2) { |a, b| a.next = b }
  cloned.last.next = nil
  Query.new(cloned.first, @jsonpath)
end

#enum_for(input) ⇒ Janeway::Enumerator

Combine this query with input to make an Enumerator. This can be used to iterate over results with #each, #map, etc.

Returns:



29
30
31
# File 'lib/janeway/query.rb', line 29

def enum_for(input)
  Janeway::Enumerator.new(self, input)
end

#interpreter_tree_for(type) { ... } ⇒ Interpreters::Base

Return a cached interpreter tree for the given type, building it via the block on the first call. Only stateless interpreter types (:finder, :deleter) benefit from caching — :iterator and :delete_if capture the caller's block, so their trees are per-call.

No-op (yields fresh every time) if the Query is frozen — respecting the docstring's "can be frozen" contract.

Parameters:

  • type (Symbol)

    :finder, :deleter, :iterator, :delete_if

Yields:

  • build the tree if not cached

Returns:



58
59
60
61
62
63
64
# File 'lib/janeway/query.rb', line 58

def interpreter_tree_for(type)
  return yield unless %i[finder deleter].include?(type)
  return yield if frozen?

  @interpreter_tree_cache ||= {}
  @interpreter_tree_cache[type] ||= yield
end

#node_listArray<Expression>

Return a list of the nodes in the AST. The AST of a jsonpath query is a straight line, so this is expressible as an array. The only part of the AST with branches is inside a filter selector, but that doesn't show up here.

Returns:

  • (Array<Expression>)


70
71
72
73
74
75
76
77
78
79
80
# File 'lib/janeway/query.rb', line 70

def node_list
  nodes = []
  node = @root
  loop do
    nodes << node
    break unless node.next

    node = node.next
  end
  nodes
end

#parent_queryArray(Query, AST::Selector)

Return a data-free split of this singular query into a "parent" Query plus the leaf selector — the parent addresses one level up from what self addresses. Used by Enumerator#insert to locate the container that a new value should be inserted into.

Returns:



147
148
149
150
151
# File 'lib/janeway/query.rb', line 147

def parent_query
  parent = dup
  leaf = parent.pop
  [parent, leaf]
end

#popAST::Selector

Delete the last element from the chain of selectors. For a singular query, this makes the query point to the match's parent instead of the match itself.

Don't do this for a non-singular query, those may contain child segments and descendant segments which would lead to different results.

Returns:

Raises:



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/janeway/query.rb', line 125

def pop
  raise Janeway::Error.new('not allowed to pop from a non-singular query', to_s) unless singular_query?

  # Sever the link to the last selector
  nodes = node_list
  if nodes.size == 1
    # Special case: cannot pop from the query "$" even though it is a singular query
    raise Janeway::Error.new('cannot pop from single-element query', to_s)
  end

  nodes[-2].next = nil

  # Return the last selector
  nodes.last
end

#singular_query?Boolean

Return true if this query can only possibly match 0 or 1 elements in any input. Such a query must be composed exclusively of the root identifier followed by name selectors and / or index selectors.

Returns:

  • (Boolean)


42
43
44
# File 'lib/janeway/query.rb', line 42

def singular_query?
  @root.singular_query?
end

#to_sString

Returns:

  • (String)


34
35
36
# File 'lib/janeway/query.rb', line 34

def to_s
  @root.to_s
end

#treeObject

Print AST in tree format Every AST class prints a 1-line representation of self, with children on separate lines



93
94
95
96
97
# File 'lib/janeway/query.rb', line 93

def tree
  result = @root.tree(0)

  result.flatten.join("\n")
end