composite_primary_keys 14.0.5 → 14.0.6

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: 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