active_record_query_fixer 0.0.7 → 0.0.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65c48f74a79cfa27fcc9c02cf33eba5dde33ce47d86e395ecb354d6819ae8943
4
- data.tar.gz: 5bbb57d47062a495de2b1c337551f7b82063ae569094ab5e0b538974b6c1b581
3
+ metadata.gz: e42bc2e925978449204db3564990728f9d82b66da3c977f74cadaf502095e1a8
4
+ data.tar.gz: 749e0f1224b32818616e91487cd93ec9ef0cd47ed3cb716be41f8748ac777289
5
5
  SHA512:
6
- metadata.gz: 0cc9451b46d0a9df30beb5c5135c733bd3df30fd0c6758c033764d74e52052b9644b7c1fc3eadfdfeb5a8c3d01acd31ecb4379882d17ae423761574da6fef3fd
7
- data.tar.gz: 2a8d48a3d80ad51d45fe2a7ff501337200e41ea2665078dc8cc380ac585941778919a861d48e4db2a5d57181734ea4f51aa270692cbcfd7c22dbb9db228ef83f
6
+ metadata.gz: 9bda979fc9bade5a2d07435866be6290eeeb20d876462de6a598e070c9d2ec636251b7bfc190a4491c5fdcd6a994a7e9a8e6362a38ba44458c0a3a7a895edb7f
7
+ data.tar.gz: 542b47e254a9001b151c534ce251df809d0be23867fd42f6146af6d1b1e5ae25db63a045a175a39420abd1bb2f36957d2cc160d1132066f0a4064aca05f436c2
@@ -1,3 +1,6 @@
1
+ require "dig_bang"
2
+ require "pg_query"
3
+
1
4
  class ActiveRecordQueryFixer
2
5
  autoload :RelationExtentions, "#{__dir__}/active_record_query_fixer/relation_extentions"
3
6
 
@@ -16,70 +19,134 @@ class ActiveRecordQueryFixer
16
19
  fix_reference_group if fix_reference_group?
17
20
  fix_order_group if fix_order_group?
18
21
  fix_order_select_distinct if fix_order_select_distinct?
22
+ fix_select_group if query.values[:select] && query.values[:group]
23
+
24
+ self
25
+ end
26
+
27
+ def fix_select_group
28
+ select_targets.each do |select_target|
29
+ fields = select_target.dig!("ResTarget", "val").dig("ColumnRef", "fields")
30
+ next if !fields || fields.length != 2
31
+
32
+ table = fields[0].dig("String", "str")
33
+ column = fields[1].dig("String", "str")
34
+
35
+ if column
36
+ # A table and a column has been selected - make sure to group by that
37
+ @query = query.group("#{table}.#{column}")
38
+ elsif fields[1].key?("A_Star")
39
+ # A table and a star has been selected - assume the primary key is called "id" and group by that
40
+ @query = query.group("#{table}.id")
41
+ end
42
+ end
43
+
19
44
  self
20
45
  end
21
46
 
22
47
  def fix_order_group
23
- @query = @query.group(@query.model.arel_table[@query.model.primary_key])
48
+ @query = query.group(query.model.arel_table[query.model.primary_key])
49
+
50
+ sort_targets.each do |sort_target|
51
+ fields = sort_target.dig("SortBy", "node", "ColumnRef", "fields")
52
+ next if !fields || fields.length != 2
24
53
 
25
- @query.values[:order]&.each do |order|
26
- @query = @query.group(extract_table_and_column_from_expression(order)) if group_by_order?(order)
54
+ table = fields.dig(0, "String", "str")
55
+ column = fields.dig(1, "String", "str")
56
+
57
+ @query = query.group("#{table}.#{column}") if table && column
27
58
  end
28
59
 
29
60
  self
30
61
  end
31
62
 
32
63
  def fix_order_select_distinct
33
- changed = false
34
- @query.values[:order]&.each do |order|
35
- @query = @query.select("#{extract_table_and_column_from_expression(order)} AS active_record_query_fixer_#{@count_select}")
36
- changed = true
64
+ select_appends = []
65
+
66
+ sort_targets.each do |sort_target|
67
+ fields = sort_target.dig("SortBy", "node", "ColumnRef", "fields")
68
+ next if !fields || fields.length != 2
69
+
70
+ table = fields.dig(0, "String", "str")
71
+ column = fields.dig(1, "String", "str")
72
+
73
+ next if !table || !column
74
+
75
+ select_appends << "#{table}.#{column} AS active_record_query_fixer_#{@count_select}"
37
76
  @count_select += 1
38
77
  end
39
78
 
40
- @query = @query.select("#{@query.table_name}.*") if changed
79
+ # Start by prepending a wild-card select before doing the fix-selects to avoid any issues with `DISTINCT COUNT`
80
+ prepend_table_wildcard if !table_wildcard_prepended? && select_appends.any? && query.values[:select].blank?
81
+
82
+ select_appends.each do |select_append|
83
+ @query = query.select(select_append)
84
+ end
85
+
41
86
  self
42
87
  end
43
88
 
44
89
  def fix_reference_group
45
- @query = @query.group(@query.model.arel_table[@query.model.primary_key])
90
+ @query = query.group(query.model.arel_table[query.model.primary_key])
46
91
 
47
- @query.values[:references].each do |reference|
48
- @query = @query.group("#{reference}.id")
92
+ query.values[:references].each do |reference|
93
+ @query = query.group("#{reference}.id")
49
94
  end
95
+
96
+ self
50
97
  end
51
98
 
52
99
  private
53
100
 
54
- def extract_table_and_column_from_expression(order)
55
- if order.is_a?(Arel::Nodes::Ascending) || order.is_a?(Arel::Nodes::Descending)
56
- if order.expr.relation.respond_to?(:right)
57
- "#{order.expr.relation.right}.#{order.expr.name}"
58
- else
59
- "#{order.expr.relation.table_name}.#{order.expr.name}"
60
- end
61
- elsif order.is_a?(String)
62
- order
63
- else
64
- raise "Couldn't extract table and column from: #{order}"
65
- end
66
- end
67
-
68
101
  def fix_order_group?
69
- @query.values[:joins].blank? && @query.values[:distinct].present? ||
70
- @query.values[:group].present? && @query.values[:order].present?
102
+ query.values[:joins].blank? && query.values[:distinct].present? && query.values[:order].present? ||
103
+ query.values[:group].present? && query.values[:order].present?
71
104
  end
72
105
 
73
106
  def fix_order_select_distinct?
74
- @query.values[:distinct].present? && @query.values[:order].present?
107
+ query.values[:distinct].present? && query.values[:order].present?
75
108
  end
76
109
 
77
110
  def fix_reference_group?
78
- @query.values[:references].present? && @query.values[:group].present?
111
+ query.values[:references].present? && query.values[:group].present?
112
+ end
113
+
114
+ def parsed_query
115
+ @parsed_query ||= PgQuery.parse(query.to_sql)
116
+ end
117
+
118
+ # Prepends 'table_name.*' to the query. It needs to be pre-pended in case a `COUNT` or another aggregate function has been added to work with `DISTINCT`.
119
+ def prepend_table_wildcard
120
+ old_select = query.values[:select]&.clone || []
121
+ old_select = old_select.keep_if { |select_statement| select_statement != select_table_wildcard_sql }
122
+
123
+ @query = query.except(:select).select(select_table_wildcard_sql)
124
+
125
+ old_select.each do |select_statement|
126
+ @query = query.select(select_statement)
127
+ end
128
+ end
129
+
130
+ def select_table_wildcard_sql
131
+ @select_table_wildcard_sql ||= "#{query.table_name}.*"
132
+ end
133
+
134
+ def table_wildcard_prepended?
135
+ query.values[:select]&.first == select_table_wildcard_sql
79
136
  end
80
137
 
81
- def group_by_order?(order)
82
- order.is_a?(String) && !order.match?(/\A\s*(COUNT|SUM)\(/i)
138
+ def select_statement
139
+ @select_statement ||= parsed_query.tree.dig!(0, "RawStmt", "stmt", "SelectStmt")
140
+ end
141
+
142
+ def select_targets
143
+ @select_targets ||= select_statement.fetch("targetList")
144
+ end
145
+
146
+ def sort_targets
147
+ return [] unless select_statement.key?("sortClause")
148
+
149
+ @sort_targets ||= parsed_query.tree.dig!(0, "RawStmt", "stmt", "SelectStmt", "sortClause")
83
150
  end
84
151
  end
85
152
 
@@ -1,3 +1,3 @@
1
1
  class ActiveRecordQueryFixer
2
- VERSION = "0.0.7".freeze
2
+ VERSION = "0.0.12".freeze
3
3
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_query_fixer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - kaspernj
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-17 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2020-06-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dig_bang
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg_query
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'
13
41
  description: A library for automatically added `.select` on a column used for `.distinct`
14
42
  or automatically adding `.group` for a column used for order.
15
43
  email:
@@ -44,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
44
72
  - !ruby/object:Gem::Version
45
73
  version: '0'
46
74
  requirements: []
47
- rubygems_version: 3.0.4
75
+ rubygems_version: 3.0.6
48
76
  signing_key:
49
77
  specification_version: 4
50
78
  summary: A library for automatically added `.select` on a column used for `.distinct`