Class: SqlTools::Query

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeQuery

Returns a new instance of Query.



6
7
8
9
# File 'lib/sql_tools/query.rb', line 6

def initialize
  @common_table_expressions = {}
  @join_nodes = []
end

Instance Attribute Details

#common_table_expressionsObject (readonly)

Returns the value of attribute common_table_expressions.



4
5
6
# File 'lib/sql_tools/query.rb', line 4

def common_table_expressions
  @common_table_expressions
end

#fromObject

Returns the value of attribute from.



3
4
5
# File 'lib/sql_tools/query.rb', line 3

def from
  @from
end

#join_nodesObject

Returns the value of attribute join_nodes.



3
4
5
# File 'lib/sql_tools/query.rb', line 3

def join_nodes
  @join_nodes
end

#selectObject

Returns the value of attribute select.



3
4
5
# File 'lib/sql_tools/query.rb', line 3

def select
  @select
end

Instance Method Details

#joinsObject



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/sql_tools/query.rb', line 50

def joins
  join_nodes.map do |join_node|
    object_name = join_node.find_node(<<~QUERY).text
      (join
        (relation
         (object_reference name: (identifier) @object_name)))
    QUERY

    filter = PredicateFilter.new(self)
    object = object_alias_map[object_name]
    predicate = filter.filter(object)

    if join_node.children.any? { |child| child.type == :keyword_left }
      LeftJoin.new(object, predicate)
    else
      InnerJoin.new(object, predicate)
    end
  end
end

#object_alias_mapObject



112
113
114
115
116
117
118
119
120
121
# File 'lib/sql_tools/query.rb', line 112

def object_alias_map
  @table_alias_map ||= @from.query("(relation) @relation").each_with_object({}) do |captures, map|
    relation = captures["relation"]
    relation_name = relation.children.first.name.text
    relation_alias = relation.respond_to?(:alias) ? relation.alias.text : relation_name

    map[relation_alias] = common_table_expressions[relation_name] || Table.new(relation_name, relation_alias)
    map[relation_name] = map[relation_alias]
  end
end

#objectsObject



110
# File 'lib/sql_tools/query.rb', line 110

def objects = object_alias_map.values.to_set

#predicateObject



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/sql_tools/query.rb', line 80

def predicate
  @predicate ||= begin
    nodes = from.query(<<~QUERY).map { |captures| captures["predicate"] }
      (from
        (join
          predicate: (_) @predicate))
        (where
          predicate: (_) @predicate)
    QUERY

    builder = Predicate::Builder.new(self)
    predicates = nodes.flat_map do |predicate|
      visitor = PredicateVisitor.new(predicate).visit
      binding.b unless visitor.stack.size == 1
      builder.build(visitor.stack.last)
    end

    right = predicates.pop

    # This needs to pluck the left & right from binary expressions & rebuild the tree.
    # TODO: maybe this is the rotate algorithm, TBD
    while left = predicates.pop
      predicate = Predicate::Binary.new(left, "AND", right)
      right = predicate
    end

    right
  end
end

#relationsObject



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

def relations
  objects.each_with_object({}) do |object, map|
    map[object] ||= []
    map[object] << predicates.select do |p|
      (p.left.is_a?(Column) && p.left.table == object) ||
        (p.right.is_a?(Column) && p.right.table == object)
    end
  end
end

#selectionsObject



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/sql_tools/query.rb', line 11

def selections
  terms = select.query(<<~QUERY)
    (select_expression
      (term
        value: [
          (field
            (object_reference name: (identifier) @table_alias)?
            name: (identifier) @column_name)
          (invocation) @invocation
          (all_fields
            (object_reference name: (identifier) @table_alias)?) @all_fields
        ]
        alias: (identifier)? @selection_name))
  QUERY
  terms.map! do |captures|
    selection_name = captures["selection_name"]&.text || captures["column_name"]&.text

    if captures["all_fields"]
      table = if table_alias = captures["table_alias"]
        object_alias_map[table_alias.text]
      elsif objects.size == 1
        objects.first
      end
      Selection::AllFields.new(table)
    elsif table_alias = captures["table_alias"]
      table = object_alias_map[table_alias.text]
      column_name = captures["column_name"].text
      Selection::Column.new(selection_name, Column.new(table, column_name))
    elsif (column_name = captures["column_name"]&.text) && objects.size == 1
      table = objects.first
      Selection::Column.new(selection_name, Column.new(table, column_name))
    elsif invocation = captures["invocation"]
      Selection::Invocation.new(selection_name, invocation)
    else
      raise "Unknown selection type"
    end
  end
end