composite_primary_keys 12.0.3 → 12.0.4
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 +4 -4
- data/History.rdoc +5 -0
- data/lib/composite_primary_keys/connection_adapters/mysql/database_statements.rb +1 -1
- data/lib/composite_primary_keys/core.rb +1 -1
- data/lib/composite_primary_keys/relation.rb +80 -14
- data/lib/composite_primary_keys/relation/finder_methods.rb +1 -1
- data/lib/composite_primary_keys/version.rb +1 -1
- data/test/abstract_unit.rb +1 -1
- data/test/fixtures/db_definitions/mysql.sql +1 -1
- data/test/test_find.rb +12 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7659741f962a45ac7c557ae8dcb77a01bb361bd46ac8032f9dd4c05a89b14962
|
4
|
+
data.tar.gz: e88d4c5c7e8dee12e00b8d339ec5397d28f104bfb8ea526e979ad45101f0225d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f99e8ab455cf07e5030a94cf87566143486027e359ca97709aca115350abe0f2e263b4d459134c1f032fd14e046a598e144e7107b5ba2d41d5ad182591cb205e
|
7
|
+
data.tar.gz: 0ace5f54f8e1338393cf1e86e24ebe7fa7e414215fe7fdcfb2f4c7059ae6ebf40f38e4fc6aca3a20357e5794f4dbdb93d668da24c3aa8eb947b0ea35f9505f63
|
data/History.rdoc
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
== 12.0.4 (2020-12-30)
|
2
|
+
* Fix compatibility with Ruby Ruby 2.6 and below (ta1kt0me)
|
3
|
+
* Finally get SQLServer mass updates and deletes working (Charlie Savage)
|
4
|
+
* Fix MySQL mass updates and deletes that were broken by 12.0.3 (Charlie Savage)
|
5
|
+
|
1
6
|
== 12.0.3 (2020-11-11)
|
2
7
|
* Prevents infinite loops with gems which modify the 'attributes' method (Nicholas Guarino)
|
3
8
|
* Improve delete_all and update_all queries (Charlie Savage)
|
@@ -40,7 +40,7 @@ module ActiveRecord
|
|
40
40
|
|
41
41
|
record = statement.execute([id], connection)&.first
|
42
42
|
unless record
|
43
|
-
raise RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
|
43
|
+
raise ::ActiveRecord::RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
|
44
44
|
end
|
45
45
|
record
|
46
46
|
end
|
@@ -28,7 +28,7 @@ module ActiveRecord
|
|
28
28
|
# CPK
|
29
29
|
if @klass.composite?
|
30
30
|
stmt.table(arel_table)
|
31
|
-
|
31
|
+
cpk_subquery(stmt)
|
32
32
|
else
|
33
33
|
stmt.table(arel.join_sources.empty? ? table : arel.source)
|
34
34
|
stmt.key = arel_attribute(primary_key)
|
@@ -71,7 +71,7 @@ module ActiveRecord
|
|
71
71
|
|
72
72
|
if @klass.composite?
|
73
73
|
stmt.from(arel_table)
|
74
|
-
|
74
|
+
cpk_subquery(stmt)
|
75
75
|
else
|
76
76
|
stmt.from(arel.join_sources.empty? ? table : arel.source)
|
77
77
|
stmt.key = arel_attribute(primary_key)
|
@@ -89,6 +89,29 @@ module ActiveRecord
|
|
89
89
|
end
|
90
90
|
|
91
91
|
# CPK
|
92
|
+
def cpk_subquery(stmt)
|
93
|
+
# For update and delete statements we need a way to specify which records should
|
94
|
+
# get updated. By default, Rails creates a nested IN subquery that uses the primary
|
95
|
+
# key. Postgresql, Sqlite, MariaDb and Oracle support IN subqueries with multiple
|
96
|
+
# columns but MySQL and SqlServer do not. Instead SQL server supports EXISTS queries
|
97
|
+
# and MySQL supports obfuscated IN queries. Thus we need to check the type of
|
98
|
+
# database adapter to decide how to proceed.
|
99
|
+
if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
|
100
|
+
cpk_mysql_subquery(stmt)
|
101
|
+
elsif defined?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
|
102
|
+
cpk_exists_subquery(stmt)
|
103
|
+
else
|
104
|
+
cpk_in_subquery(stmt)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Used by postgresql, sqlite, mariadb and oracle. Example query:
|
109
|
+
#
|
110
|
+
# UPDATE reference_codes
|
111
|
+
# SET ...
|
112
|
+
# WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
|
113
|
+
# (SELECT reference_codes.reference_type_id, reference_codes.reference_code
|
114
|
+
# FROM reference_codes)
|
92
115
|
def cpk_in_subquery(stmt)
|
93
116
|
# Setup the subquery
|
94
117
|
subquery = arel.clone
|
@@ -103,25 +126,68 @@ module ActiveRecord
|
|
103
126
|
stmt.wheres = [where]
|
104
127
|
end
|
105
128
|
|
129
|
+
# CPK. This is an alternative to IN subqueries. It is used by sqlserver.
|
130
|
+
# Example query:
|
131
|
+
#
|
132
|
+
# UPDATE reference_codes
|
133
|
+
# SET ...
|
134
|
+
# WHERE EXISTS
|
135
|
+
# (SELECT 1
|
136
|
+
# FROM reference_codes cpk_child
|
137
|
+
# WHERE reference_codes.reference_type_id = cpk_child.reference_type_id AND
|
138
|
+
# reference_codes.reference_code = cpk_child.reference_code)
|
106
139
|
def cpk_exists_subquery(stmt)
|
107
|
-
|
108
|
-
|
109
|
-
|
140
|
+
arel_attributes = primary_keys.map do |key|
|
141
|
+
arel_attribute(key)
|
142
|
+
end.to_composite_keys
|
110
143
|
|
111
|
-
#
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
144
|
+
# Clone the query
|
145
|
+
subselect = arel.clone
|
146
|
+
|
147
|
+
# Alias the table - we assume just one table
|
148
|
+
aliased_table = subselect.froms.first
|
149
|
+
aliased_table.table_alias = "cpk_child"
|
150
|
+
|
151
|
+
# Project - really we could just set this to "1"
|
152
|
+
subselect.projections = arel_attributes
|
116
153
|
|
117
154
|
# Setup correlation to the outer query via where clauses
|
118
155
|
primary_keys.map do |key|
|
119
|
-
outer_attribute =
|
120
|
-
inner_attribute =
|
156
|
+
outer_attribute = arel_table[key]
|
157
|
+
inner_attribute = aliased_table[key]
|
121
158
|
where = outer_attribute.eq(inner_attribute)
|
122
|
-
|
159
|
+
subselect.where(where)
|
123
160
|
end
|
124
|
-
stmt.wheres = [Arel::Nodes::Exists.new(
|
161
|
+
stmt.wheres = [Arel::Nodes::Exists.new(subselect)]
|
162
|
+
end
|
163
|
+
|
164
|
+
# CPK. This is the old way CPK created subqueries and is used by MySql.
|
165
|
+
# MySQL does not support referencing the same table that is being UPDATEd or
|
166
|
+
# DELETEd in a subquery so we obfuscate it. The ugly query looks like this:
|
167
|
+
#
|
168
|
+
# UPDATE `reference_codes`
|
169
|
+
# SET ...
|
170
|
+
# WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
|
171
|
+
# (SELECT reference_type_id,reference_code
|
172
|
+
# FROM (SELECT DISTINCT `reference_codes`.`reference_type_id`, `reference_codes`.`reference_code`
|
173
|
+
# FROM `reference_codes`) __active_record_temp)
|
174
|
+
def cpk_mysql_subquery(stmt)
|
175
|
+
arel_attributes = primary_keys.map do |key|
|
176
|
+
arel_attribute(key)
|
177
|
+
end.to_composite_keys
|
178
|
+
|
179
|
+
subselect = arel.clone
|
180
|
+
subselect.projections = arel_attributes
|
181
|
+
|
182
|
+
# Materialize subquery by adding distinct
|
183
|
+
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
184
|
+
subselect.distinct unless arel.limit || arel.offset || arel.orders.any?
|
185
|
+
|
186
|
+
key_name = arel_attributes.map(&:name).join(',')
|
187
|
+
|
188
|
+
manager = Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
|
189
|
+
|
190
|
+
stmt.wheres = [Arel::Nodes::In.new(arel_attributes, manager.ast)]
|
125
191
|
end
|
126
192
|
end
|
127
193
|
end
|
@@ -96,7 +96,7 @@ module CompositePrimaryKeys
|
|
96
96
|
case ids.size
|
97
97
|
when 0
|
98
98
|
error_message = "Couldn't find #{model_name} without an ID"
|
99
|
-
raise RecordNotFound.new(error_message, model_name, primary_key)
|
99
|
+
raise ::ActiveRecord::RecordNotFound.new(error_message, model_name, primary_key)
|
100
100
|
when 1
|
101
101
|
result = find_one(ids.first)
|
102
102
|
expects_array ? [ result ] : result
|
data/test/abstract_unit.rb
CHANGED
data/test/test_find.rb
CHANGED
@@ -74,6 +74,18 @@ class TestFind < ActiveSupport::TestCase
|
|
74
74
|
assert_equal(with_quoted_identifiers(expected), error.message)
|
75
75
|
end
|
76
76
|
|
77
|
+
def test_find_with_invalid_ids
|
78
|
+
assert_raise(::ActiveRecord::RecordNotFound) do
|
79
|
+
Suburb.find([-1, -1])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_find_with_no_ids
|
84
|
+
assert_raise(::ActiveRecord::RecordNotFound) do
|
85
|
+
Suburb.find
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
77
89
|
def test_find_last_suburb
|
78
90
|
suburb = Suburb.last
|
79
91
|
assert_equal([2,2], suburb.id)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: composite_primary_keys
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 12.0.
|
4
|
+
version: 12.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charlie Savage
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|