composite_primary_keys 14.0.5 → 14.0.6

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: 4feff3a4f8ce272695c7a081666b99f3e9b79b206dce5acf9728c0c2c0cb2d9a
4
- data.tar.gz: f5d7533a624427d056ad9fd7576242a2b6318ed584939bea834de44bd8d618af
3
+ metadata.gz: 76a010358e1d2cacd9b05f6229c1fe4225f20f888fefb57b0bd6a9f62589a720
4
+ data.tar.gz: 1b7870c774bbed21d4556cf53518054860903cae4a0987f5978e02fc3883597f
5
5
  SHA512:
6
- metadata.gz: a2e52091afe3577f46df1e0093ad6d4d6167a250e27ec7776af7706b8ec01fce0539f15a0bcaa1d12273ac2ed8055cddcc6fef5536b5e7ae8761d4158c2f9a6a
7
- data.tar.gz: 8159181dd45c05ba6aad2a57ff482bdf8471904d6990517e04a992bc177e42b5cb201dfb48ff7e645ecfb2e23270ed129a9a6666b87a6412d07a3163200e4d72
6
+ metadata.gz: 117d7be0b54619f7adcb05ddb200c3c97571cc74bf2fcf33c99034efaec08924cf6d56e998db1135c92bf71497697b30ca531a882d755329d5b6f9192d606013
7
+ data.tar.gz: a9f330398e5b1a9153a67f229a28e83d65c5bc02334e6e9f3456229c4c1e011a3bf03909fa45e37739d4ab47a37663a3ee6ef09c7d7194721e91849e520c0a01
data/History.rdoc CHANGED
@@ -1,3 +1,7 @@
1
+ == 14.0.6 (2023-02-04)
2
+ * Port fix for #573 (Charlie Savage)
3
+ * Port fix for fix #577 (Charlie Savage)
4
+
1
5
  == 14.0.5 (2023-02-04)
2
6
  * Improve query generation for cpk_in_predicate. This reduces the length of
3
7
  queries when loading many keys and enables Postgres to use index scans
@@ -27,6 +31,17 @@ more frequently. (Andrew Kiellor)
27
31
  == 14.0.0 (2022-01-9)
28
32
  * Update to ActiveRecord 7.0 (Sammy Larbi)
29
33
 
34
+ == 13.0.7 (2023-02-04)
35
+ * Fix #573 (Charlie Savage)
36
+
37
+ == 13.0.6 (2023-02-04)
38
+ * Fix #577 (Charlie Savage)
39
+
40
+ == 13.0.5 (2023-02-04)
41
+ * Improve query generation for cpk_in_predicate. This reduces the length of
42
+ queries when loading many keys and enables Postgres to use index scans
43
+ more frequently. (Andrew Kiellor)
44
+
30
45
  == 13.0.4 (2022-12-05)
31
46
  * Fix previously_new_record? not being set to true after create (Akinori MUSHA)
32
47
 
@@ -46,22 +46,6 @@ module ActiveRecord
46
46
  (result[key] ||= []) << owner if key
47
47
  end
48
48
  end
49
-
50
- # TODO: is records_by_owner needed anymore? Rails' implementation has changed significantly
51
- def records_by_owner
52
- @records_by_owner ||= preloaded_records.each_with_object({}) do |record, result|
53
- key = if association_key_name.is_a?(Array)
54
- Array(record[association_key_name]).map do |key|
55
- convert_key(key)
56
- end
57
- else
58
- convert_key(record[association_key_name])
59
- end
60
- owners_by_key[key].each do |owner|
61
- (result[owner] ||= []) << record
62
- end
63
- end
64
- end
65
49
  end
66
50
  end
67
51
  end
@@ -1,197 +1,197 @@
1
- module ActiveRecord
2
- class Relation
3
- alias :initialize_without_cpk :initialize
4
- def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
5
- initialize_without_cpk(klass, table: table, predicate_builder: predicate_builder, values: values)
6
- add_cpk_support if klass && klass.composite?
7
- end
8
-
9
- alias :initialize_copy_without_cpk :initialize_copy
10
- def initialize_copy(other)
11
- initialize_copy_without_cpk(other)
12
- add_cpk_support if klass.composite?
13
- end
14
-
15
- def add_cpk_support
16
- extend CompositePrimaryKeys::CompositeRelation
17
- end
18
-
19
- def update_all(updates)
20
- raise ArgumentError, "Empty list of attributes to change" if updates.blank?
21
-
22
- if eager_loading?
23
- relation = apply_join_dependency
24
- return relation.update_all(updates)
25
- end
26
-
27
- stmt = Arel::UpdateManager.new
28
- stmt.table(arel.join_sources.empty? ? table : arel.source)
29
- stmt.key = table[primary_key]
30
-
31
- # CPK
32
- if @klass.composite? && @klass.connection.visitor.compile(stmt.ast) =~ /['"]#{primary_key.to_s}['"]/
33
- stmt = Arel::UpdateManager.new
34
- stmt.table(arel_table)
35
- cpk_subquery(stmt)
36
- else
37
- stmt.wheres = arel.constraints
38
- end
39
- stmt.take(arel.limit)
40
- stmt.offset(arel.offset)
41
- stmt.order(*arel.orders)
42
-
43
- if updates.is_a?(Hash)
44
- if klass.locking_enabled? &&
45
- !updates.key?(klass.locking_column) &&
46
- !updates.key?(klass.locking_column.to_sym)
47
- attr = table[klass.locking_column]
48
- updates[attr.name] = _increment_attribute(attr)
49
- end
50
- stmt.set _substitute_values(updates)
51
- else
52
- stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
53
- end
54
-
55
- @klass.connection.update stmt, "#{@klass} Update All"
56
- end
57
-
58
- def delete_all
59
- invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
60
- value = @values[method]
61
- method == :distinct ? value : value&.any?
62
- end
63
- if invalid_methods.any?
64
- raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
65
- end
66
-
67
- if eager_loading?
68
- relation = apply_join_dependency
69
- return relation.delete_all
70
- end
71
-
72
- stmt = Arel::DeleteManager.new
73
- stmt.from(arel.join_sources.empty? ? table : arel.source)
74
- stmt.key = table[primary_key]
75
-
76
- # CPK
77
- if @klass.composite? && @klass.connection.visitor.compile(stmt.ast) =~ /['"]#{primary_key.to_s}['"]/
78
- stmt = Arel::DeleteManager.new
79
- stmt.from(arel_table)
80
- cpk_subquery(stmt)
81
- else
82
- stmt.wheres = arel.constraints
83
- end
84
-
85
- stmt.take(arel.limit)
86
- stmt.offset(arel.offset)
87
- stmt.order(*arel.orders)
88
-
89
- affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
90
-
91
- reset
92
- affected
93
- end
94
-
95
- # CPK
96
- def cpk_subquery(stmt)
97
- # For update and delete statements we need a way to specify which records should
98
- # get updated. By default, Rails creates a nested IN subquery that uses the primary
99
- # key. Postgresql, Sqlite, MariaDb and Oracle support IN subqueries with multiple
100
- # columns but MySQL and SqlServer do not. Instead SQL server supports EXISTS queries
101
- # and MySQL supports obfuscated IN queries. Thus we need to check the type of
102
- # database adapter to decide how to proceed.
103
- if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
104
- cpk_mysql_subquery(stmt)
105
- elsif defined?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
106
- cpk_exists_subquery(stmt)
107
- else
108
- cpk_in_subquery(stmt)
109
- end
110
- end
111
-
112
- # Used by postgresql, sqlite, mariadb and oracle. Example query:
113
- #
114
- # UPDATE reference_codes
115
- # SET ...
116
- # WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
117
- # (SELECT reference_codes.reference_type_id, reference_codes.reference_code
118
- # FROM reference_codes)
119
- def cpk_in_subquery(stmt)
120
- # Setup the subquery
121
- subquery = arel.clone
122
- subquery.projections = primary_keys.map do |key|
123
- arel_table[key]
124
- end
125
-
126
- where_fields = primary_keys.map do |key|
127
- arel_table[key]
128
- end
129
- where = Arel::Nodes::Grouping.new(where_fields).in(subquery)
130
- stmt.wheres = [where]
131
- end
132
-
133
- # CPK. This is an alternative to IN subqueries. It is used by sqlserver.
134
- # Example query:
135
- #
136
- # UPDATE reference_codes
137
- # SET ...
138
- # WHERE EXISTS
139
- # (SELECT 1
140
- # FROM reference_codes cpk_child
141
- # WHERE reference_codes.reference_type_id = cpk_child.reference_type_id AND
142
- # reference_codes.reference_code = cpk_child.reference_code)
143
- def cpk_exists_subquery(stmt)
144
- arel_attributes = primary_keys.map do |key|
145
- table[key]
146
- end.to_composite_keys
147
-
148
- # Clone the query
149
- subselect = arel.clone
150
-
151
- # Alias the table - we assume just one table
152
- aliased_table = subselect.froms.first
153
- aliased_table.table_alias = "cpk_child"
154
-
155
- # Project - really we could just set this to "1"
156
- subselect.projections = arel_attributes
157
-
158
- # Setup correlation to the outer query via where clauses
159
- primary_keys.map do |key|
160
- outer_attribute = arel_table[key]
161
- inner_attribute = aliased_table[key]
162
- where = outer_attribute.eq(inner_attribute)
163
- subselect.where(where)
164
- end
165
- stmt.wheres = [Arel::Nodes::Exists.new(subselect)]
166
- end
167
-
168
- # CPK. This is the old way CPK created subqueries and is used by MySql.
169
- # MySQL does not support referencing the same table that is being UPDATEd or
170
- # DELETEd in a subquery so we obfuscate it. The ugly query looks like this:
171
- #
172
- # UPDATE `reference_codes`
173
- # SET ...
174
- # WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
175
- # (SELECT reference_type_id,reference_code
176
- # FROM (SELECT DISTINCT `reference_codes`.`reference_type_id`, `reference_codes`.`reference_code`
177
- # FROM `reference_codes`) __active_record_temp)
178
- def cpk_mysql_subquery(stmt)
179
- arel_attributes = primary_keys.map do |key|
180
- table[key]
181
- end.to_composite_keys
182
-
183
- subselect = arel.clone
184
- subselect.projections = arel_attributes
185
-
186
- # Materialize subquery by adding distinct
187
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
188
- subselect.distinct unless arel.limit || arel.offset || arel.orders.any?
189
-
190
- key_name = arel_attributes.map(&:name).join(',')
191
-
192
- manager = Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
193
-
194
- stmt.wheres = [Arel::Nodes::In.new(arel_attributes, manager.ast)]
195
- end
196
- end
197
- end
1
+ module ActiveRecord
2
+ class Relation
3
+ alias :initialize_without_cpk :initialize
4
+ def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
5
+ initialize_without_cpk(klass, table: table, predicate_builder: predicate_builder, values: values)
6
+ add_cpk_support if klass && klass.composite?
7
+ end
8
+
9
+ alias :initialize_copy_without_cpk :initialize_copy
10
+ def initialize_copy(other)
11
+ initialize_copy_without_cpk(other)
12
+ add_cpk_support if klass.composite?
13
+ end
14
+
15
+ def add_cpk_support
16
+ extend CompositePrimaryKeys::CompositeRelation
17
+ end
18
+
19
+ def update_all(updates)
20
+ raise ArgumentError, "Empty list of attributes to change" if updates.blank?
21
+
22
+ if eager_loading?
23
+ relation = apply_join_dependency
24
+ return relation.update_all(updates)
25
+ end
26
+
27
+ stmt = Arel::UpdateManager.new
28
+ stmt.table(arel.join_sources.empty? ? table : arel.source)
29
+ stmt.key = table[primary_key]
30
+
31
+ # CPK
32
+ if @klass.composite?
33
+ stmt = Arel::UpdateManager.new
34
+ stmt.table(arel_table)
35
+ cpk_subquery(stmt)
36
+ else
37
+ stmt.wheres = arel.constraints
38
+ end
39
+ stmt.take(arel.limit)
40
+ stmt.offset(arel.offset)
41
+ stmt.order(*arel.orders)
42
+
43
+ if updates.is_a?(Hash)
44
+ if klass.locking_enabled? &&
45
+ !updates.key?(klass.locking_column) &&
46
+ !updates.key?(klass.locking_column.to_sym)
47
+ attr = table[klass.locking_column]
48
+ updates[attr.name] = _increment_attribute(attr)
49
+ end
50
+ stmt.set _substitute_values(updates)
51
+ else
52
+ stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
53
+ end
54
+
55
+ @klass.connection.update stmt, "#{@klass} Update All"
56
+ end
57
+
58
+ def delete_all
59
+ invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
60
+ value = @values[method]
61
+ method == :distinct ? value : value&.any?
62
+ end
63
+ if invalid_methods.any?
64
+ raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
65
+ end
66
+
67
+ if eager_loading?
68
+ relation = apply_join_dependency
69
+ return relation.delete_all
70
+ end
71
+
72
+ stmt = Arel::DeleteManager.new
73
+ stmt.from(arel.join_sources.empty? ? table : arel.source)
74
+ stmt.key = table[primary_key]
75
+
76
+ # CPK
77
+ if @klass.composite?
78
+ stmt = Arel::DeleteManager.new
79
+ stmt.from(arel_table)
80
+ cpk_subquery(stmt)
81
+ else
82
+ stmt.wheres = arel.constraints
83
+ end
84
+
85
+ stmt.take(arel.limit)
86
+ stmt.offset(arel.offset)
87
+ stmt.order(*arel.orders)
88
+
89
+ affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
90
+
91
+ reset
92
+ affected
93
+ end
94
+
95
+ # CPK
96
+ def cpk_subquery(stmt)
97
+ # For update and delete statements we need a way to specify which records should
98
+ # get updated. By default, Rails creates a nested IN subquery that uses the primary
99
+ # key. Postgresql, Sqlite, MariaDb and Oracle support IN subqueries with multiple
100
+ # columns but MySQL and SqlServer do not. Instead SQL server supports EXISTS queries
101
+ # and MySQL supports obfuscated IN queries. Thus we need to check the type of
102
+ # database adapter to decide how to proceed.
103
+ if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
104
+ cpk_mysql_subquery(stmt)
105
+ elsif defined?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
106
+ cpk_exists_subquery(stmt)
107
+ else
108
+ cpk_in_subquery(stmt)
109
+ end
110
+ end
111
+
112
+ # Used by postgresql, sqlite, mariadb and oracle. Example query:
113
+ #
114
+ # UPDATE reference_codes
115
+ # SET ...
116
+ # WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
117
+ # (SELECT reference_codes.reference_type_id, reference_codes.reference_code
118
+ # FROM reference_codes)
119
+ def cpk_in_subquery(stmt)
120
+ # Setup the subquery
121
+ subquery = arel.clone
122
+ subquery.projections = primary_keys.map do |key|
123
+ arel_table[key]
124
+ end
125
+
126
+ where_fields = primary_keys.map do |key|
127
+ arel_table[key]
128
+ end
129
+ where = Arel::Nodes::Grouping.new(where_fields).in(subquery)
130
+ stmt.wheres = [where]
131
+ end
132
+
133
+ # CPK. This is an alternative to IN subqueries. It is used by sqlserver.
134
+ # Example query:
135
+ #
136
+ # UPDATE reference_codes
137
+ # SET ...
138
+ # WHERE EXISTS
139
+ # (SELECT 1
140
+ # FROM reference_codes cpk_child
141
+ # WHERE reference_codes.reference_type_id = cpk_child.reference_type_id AND
142
+ # reference_codes.reference_code = cpk_child.reference_code)
143
+ def cpk_exists_subquery(stmt)
144
+ arel_attributes = primary_keys.map do |key|
145
+ table[key]
146
+ end.to_composite_keys
147
+
148
+ # Clone the query
149
+ subselect = arel.clone
150
+
151
+ # Alias the table - we assume just one table
152
+ aliased_table = subselect.froms.first
153
+ aliased_table.table_alias = "cpk_child"
154
+
155
+ # Project - really we could just set this to "1"
156
+ subselect.projections = arel_attributes
157
+
158
+ # Setup correlation to the outer query via where clauses
159
+ primary_keys.map do |key|
160
+ outer_attribute = arel_table[key]
161
+ inner_attribute = aliased_table[key]
162
+ where = outer_attribute.eq(inner_attribute)
163
+ subselect.where(where)
164
+ end
165
+ stmt.wheres = [Arel::Nodes::Exists.new(subselect)]
166
+ end
167
+
168
+ # CPK. This is the old way CPK created subqueries and is used by MySql.
169
+ # MySQL does not support referencing the same table that is being UPDATEd or
170
+ # DELETEd in a subquery so we obfuscate it. The ugly query looks like this:
171
+ #
172
+ # UPDATE `reference_codes`
173
+ # SET ...
174
+ # WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
175
+ # (SELECT reference_type_id,reference_code
176
+ # FROM (SELECT DISTINCT `reference_codes`.`reference_type_id`, `reference_codes`.`reference_code`
177
+ # FROM `reference_codes`) __active_record_temp)
178
+ def cpk_mysql_subquery(stmt)
179
+ arel_attributes = primary_keys.map do |key|
180
+ table[key]
181
+ end.to_composite_keys
182
+
183
+ subselect = arel.clone
184
+ subselect.projections = arel_attributes
185
+
186
+ # Materialize subquery by adding distinct
187
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
188
+ subselect.distinct unless arel.limit || arel.offset || arel.orders.any?
189
+
190
+ key_name = arel_attributes.map(&:name).join(',')
191
+
192
+ manager = Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
193
+
194
+ stmt.wheres = [Arel::Nodes::In.new(arel_attributes, manager.ast)]
195
+ end
196
+ end
197
+ end
@@ -2,7 +2,7 @@ module CompositePrimaryKeys
2
2
  module VERSION
3
3
  MAJOR = 14
4
4
  MINOR = 0
5
- TINY = 5
5
+ TINY = 6
6
6
  STRING = [MAJOR, MINOR, TINY].join('.')
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: composite_primary_keys
3
3
  version: !ruby/object:Gem::Version
4
- version: 14.0.5
4
+ version: 14.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charlie Savage