active_record_query_fixer 0.0.7 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
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`