composite_primary_keys 12.0.2 → 12.0.7
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.
Potentially problematic release.
This version of composite_primary_keys might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.rdoc +30 -0
- data/README.rdoc +3 -2
- data/lib/composite_primary_keys.rb +57 -56
- data/lib/composite_primary_keys/active_model/attribute_assignment.rb +19 -0
- data/lib/composite_primary_keys/arel/sqlserver.rb +1 -3
- data/lib/composite_primary_keys/associations/through_association.rb +2 -1
- data/lib/composite_primary_keys/attribute_methods.rb +1 -1
- data/lib/composite_primary_keys/attribute_methods/primary_key.rb +13 -0
- data/lib/composite_primary_keys/base.rb +11 -0
- data/lib/composite_primary_keys/composite_arrays.rb +0 -8
- data/lib/composite_primary_keys/connection_adapters/abstract/database_statements.rb +24 -4
- data/lib/composite_primary_keys/connection_adapters/mysql/database_statements.rb +24 -0
- data/lib/composite_primary_keys/connection_adapters/sqlserver/database_statements.rb +32 -11
- data/lib/composite_primary_keys/core.rb +1 -1
- data/lib/composite_primary_keys/persistence.rb +2 -2
- data/lib/composite_primary_keys/relation.rb +100 -25
- data/lib/composite_primary_keys/relation/batches.rb +1 -1
- data/lib/composite_primary_keys/relation/finder_methods.rb +1 -1
- data/lib/composite_primary_keys/relation/predicate_builder/association_query_value.rb +1 -1
- data/lib/composite_primary_keys/version.rb +1 -1
- data/test/abstract_unit.rb +2 -1
- data/test/connections/databases.ci.yml +5 -2
- data/test/fixtures/article.rb +4 -0
- data/test/fixtures/articles.yml +4 -3
- data/test/fixtures/comment.rb +1 -3
- data/test/fixtures/comments.yml +10 -9
- data/test/fixtures/db_definitions/db2-create-tables.sql +0 -14
- data/test/fixtures/db_definitions/db2-drop-tables.sql +1 -3
- data/test/fixtures/db_definitions/mysql.sql +7 -44
- data/test/fixtures/db_definitions/oracle.drop.sql +3 -9
- data/test/fixtures/db_definitions/oracle.sql +12 -48
- data/test/fixtures/db_definitions/postgresql.sql +7 -44
- data/test/fixtures/db_definitions/sqlite.sql +6 -42
- data/test/fixtures/db_definitions/sqlserver.sql +5 -41
- data/test/fixtures/department.rb +8 -3
- data/test/fixtures/departments.yml +4 -4
- data/test/fixtures/readings.yml +2 -2
- data/test/fixtures/restaurants_suburbs.yml +1 -1
- data/test/fixtures/streets.yml +2 -2
- data/test/fixtures/suburbs.yml +2 -2
- data/test/fixtures/user.rb +3 -2
- data/test/test_associations.rb +30 -23
- data/test/test_create.rb +41 -18
- data/test/test_delete.rb +3 -3
- data/test/test_exists.rb +5 -5
- data/test/test_find.rb +21 -2
- data/test/test_habtm.rb +2 -2
- data/test/test_ids.rb +2 -6
- data/test/test_nested_attributes.rb +0 -57
- data/test/test_polymorphic.rb +29 -13
- data/test/test_preload.rb +4 -3
- data/test/test_serialize.rb +2 -2
- data/test/test_update.rb +19 -1
- metadata +11 -25
- data/test/fixtures/hack.rb +0 -5
- data/test/fixtures/hacks.yml +0 -3
- data/test/fixtures/pk_called_id.rb +0 -5
- data/test/fixtures/pk_called_ids.yml +0 -11
- data/test/fixtures/reference_code_using_composite_key_alias.rb +0 -8
- data/test/fixtures/reference_code_using_simple_key_alias.rb +0 -8
- data/test/fixtures/seat.rb +0 -5
- data/test/fixtures/seats.yml +0 -9
- data/test/fixtures/topic.rb +0 -6
- data/test/fixtures/topic_source.rb +0 -7
- data/test/test_aliases.rb +0 -18
- data/test/test_enum.rb +0 -21
- data/test/test_suite.rb +0 -35
@@ -27,15 +27,8 @@ module ActiveRecord
|
|
27
27
|
stmt = Arel::UpdateManager.new
|
28
28
|
# CPK
|
29
29
|
if @klass.composite?
|
30
|
-
stmt.table(
|
31
|
-
|
32
|
-
arel_attributes = primary_keys.map do |key|
|
33
|
-
arel_attribute(key)
|
34
|
-
end.to_composite_keys
|
35
|
-
|
36
|
-
subselect = subquery_for(arel_attributes, arel)
|
37
|
-
|
38
|
-
stmt.wheres = [arel_attributes.in(subselect)]
|
30
|
+
stmt.table(arel_table)
|
31
|
+
cpk_subquery(stmt)
|
39
32
|
else
|
40
33
|
stmt.table(arel.join_sources.empty? ? table : arel.source)
|
41
34
|
stmt.key = arel_attribute(primary_key)
|
@@ -75,22 +68,16 @@ module ActiveRecord
|
|
75
68
|
end
|
76
69
|
|
77
70
|
stmt = Arel::DeleteManager.new
|
78
|
-
# CPK
|
79
|
-
if @klass.composite?
|
80
|
-
stmt.from(table)
|
81
|
-
|
82
|
-
arel_attributes = primary_keys.map do |key|
|
83
|
-
arel_attribute(key)
|
84
|
-
end.to_composite_keys
|
85
71
|
|
86
|
-
|
87
|
-
|
88
|
-
stmt
|
72
|
+
if @klass.composite?
|
73
|
+
stmt.from(arel_table)
|
74
|
+
cpk_subquery(stmt)
|
89
75
|
else
|
90
76
|
stmt.from(arel.join_sources.empty? ? table : arel.source)
|
91
77
|
stmt.key = arel_attribute(primary_key)
|
92
78
|
stmt.wheres = arel.constraints
|
93
79
|
end
|
80
|
+
|
94
81
|
stmt.take(arel.limit)
|
95
82
|
stmt.offset(arel.offset)
|
96
83
|
stmt.order(*arel.orders)
|
@@ -102,17 +89,105 @@ module ActiveRecord
|
|
102
89
|
end
|
103
90
|
|
104
91
|
# CPK
|
105
|
-
def
|
106
|
-
|
107
|
-
|
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)
|
115
|
+
def cpk_in_subquery(stmt)
|
116
|
+
# Setup the subquery
|
117
|
+
subquery = arel.clone
|
118
|
+
subquery.projections = primary_keys.map do |key|
|
119
|
+
arel_table[key]
|
120
|
+
end
|
121
|
+
|
122
|
+
where_fields = primary_keys.map do |key|
|
123
|
+
arel_table[key]
|
124
|
+
end
|
125
|
+
where = Arel::Nodes::Grouping.new(where_fields).in(subquery)
|
126
|
+
stmt.wheres = [where]
|
127
|
+
end
|
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)
|
139
|
+
def cpk_exists_subquery(stmt)
|
140
|
+
arel_attributes = primary_keys.map do |key|
|
141
|
+
arel_attribute(key)
|
142
|
+
end.to_composite_keys
|
143
|
+
|
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
|
153
|
+
|
154
|
+
# Setup correlation to the outer query via where clauses
|
155
|
+
primary_keys.map do |key|
|
156
|
+
outer_attribute = arel_table[key]
|
157
|
+
inner_attribute = aliased_table[key]
|
158
|
+
where = outer_attribute.eq(inner_attribute)
|
159
|
+
subselect.where(where)
|
160
|
+
end
|
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
|
108
181
|
|
109
182
|
# Materialize subquery by adding distinct
|
110
183
|
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
111
|
-
subselect.distinct unless
|
184
|
+
subselect.distinct unless arel.limit || arel.offset || arel.orders.any?
|
185
|
+
|
186
|
+
key_name = arel_attributes.map(&:name).join(',')
|
112
187
|
|
113
|
-
|
188
|
+
manager = Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
|
114
189
|
|
115
|
-
Arel::
|
190
|
+
stmt.wheres = [Arel::Nodes::In.new(arel_attributes, manager.ast)]
|
116
191
|
end
|
117
192
|
end
|
118
193
|
end
|
@@ -4,7 +4,7 @@ module CompositePrimaryKeys
|
|
4
4
|
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
|
5
5
|
relation = self
|
6
6
|
unless block_given?
|
7
|
-
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
|
7
|
+
return ::ActiveRecord::Batches::BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
|
8
8
|
end
|
9
9
|
|
10
10
|
if arel.orders.present?
|
@@ -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
|
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
6
6
|
if associated_table.association_join_foreign_key.is_a?(Array)
|
7
7
|
if ids.is_a?(ActiveRecord::Relation)
|
8
8
|
ids.map do |id|
|
9
|
-
id.
|
9
|
+
associated_table.association_join_foreign_key.zip(id.id).to_h
|
10
10
|
end
|
11
11
|
else
|
12
12
|
[associated_table.association_join_foreign_key.zip(ids).to_h]
|
data/test/abstract_unit.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
mysql:
|
2
2
|
adapter: mysql2
|
3
|
-
username:
|
4
|
-
password:
|
3
|
+
username: github
|
4
|
+
password: github
|
5
|
+
host: 127.0.0.1
|
6
|
+
port: 3306
|
5
7
|
encoding: utf8mb4
|
6
8
|
charset: utf8mb4
|
7
9
|
collation: utf8mb4_bin
|
@@ -11,6 +13,7 @@ postgresql:
|
|
11
13
|
adapter: postgresql
|
12
14
|
database: composite_primary_keys_unittest
|
13
15
|
username: postgres
|
16
|
+
password: postgres
|
14
17
|
host: localhost
|
15
18
|
|
16
19
|
sqlite:
|
data/test/fixtures/article.rb
CHANGED
@@ -2,5 +2,9 @@ class Article < ActiveRecord::Base
|
|
2
2
|
validates :id, uniqueness: true, numericality: true, allow_nil: true, allow_blank: true, on: :create
|
3
3
|
has_many :readings, :dependent => :delete_all
|
4
4
|
has_many :users, :through => :readings
|
5
|
+
|
6
|
+
has_many :comments, :dependent => :delete_all
|
7
|
+
has_many :employee_commentators, :through => :comments, :source => :person, :source_type => :employee
|
8
|
+
has_many :user_commentators, :through => :comments, :source => :person, :source_type => "User"
|
5
9
|
end
|
6
10
|
|
data/test/fixtures/articles.yml
CHANGED
data/test/fixtures/comment.rb
CHANGED
data/test/fixtures/comments.yml
CHANGED
@@ -1,16 +1,17 @@
|
|
1
|
-
|
1
|
+
employee_comment:
|
2
2
|
id: 1
|
3
|
+
article: first
|
3
4
|
person_id: 1
|
4
5
|
person_type: Employee
|
5
|
-
|
6
|
-
|
6
|
+
|
7
|
+
user_comment_1:
|
7
8
|
id: 2
|
9
|
+
article: second
|
8
10
|
person_id: 1
|
9
11
|
person_type: User
|
10
|
-
|
11
|
-
|
12
|
-
comment3:
|
12
|
+
|
13
|
+
user_comment_2:
|
13
14
|
id: 3
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
article: second
|
16
|
+
person_id: 2
|
17
|
+
person_type: User
|
@@ -1,17 +1,3 @@
|
|
1
|
-
CREATE TABLE topics (
|
2
|
-
id integer NOT NULL,
|
3
|
-
name varchar(50) default NULL,
|
4
|
-
feed_size integer default NULL,
|
5
|
-
PRIMARY KEY (id)
|
6
|
-
);
|
7
|
-
|
8
|
-
CREATE TABLE topic_sources (
|
9
|
-
topic_id integer NOT NULL,
|
10
|
-
platform varchar(50) NOT NULL,
|
11
|
-
keywords varchar(50) default NULL,
|
12
|
-
PRIMARY KEY (topic_id,platform)
|
13
|
-
);
|
14
|
-
|
15
1
|
CREATE TABLE reference_types (
|
16
2
|
reference_type_id integer NOT NULL generated by default as identity (start with 100, increment by 1, no cache),
|
17
3
|
type_label varchar(50) default NULL,
|
@@ -1,17 +1,3 @@
|
|
1
|
-
create table topics (
|
2
|
-
id int not null auto_increment,
|
3
|
-
name varchar(50) default null,
|
4
|
-
feed_size int default null,
|
5
|
-
primary key (id)
|
6
|
-
);
|
7
|
-
|
8
|
-
create table topic_sources (
|
9
|
-
topic_id int not null,
|
10
|
-
platform varchar(50) not null,
|
11
|
-
keywords varchar(50) default null,
|
12
|
-
primary key (topic_id,platform)
|
13
|
-
);
|
14
|
-
|
15
1
|
create table reference_types (
|
16
2
|
reference_type_id int not null auto_increment,
|
17
3
|
type_label varchar(50) default null,
|
@@ -52,7 +38,7 @@ create table product_tariffs (
|
|
52
38
|
);
|
53
39
|
|
54
40
|
create table suburbs (
|
55
|
-
city_id int not null,
|
41
|
+
city_id int not null auto_increment,
|
56
42
|
suburb_id int not null,
|
57
43
|
name varchar(50) not null,
|
58
44
|
primary key (city_id, suburb_id)
|
@@ -86,7 +72,7 @@ create table readings (
|
|
86
72
|
primary key (id)
|
87
73
|
);
|
88
74
|
|
89
|
-
create table groups (
|
75
|
+
create table `groups` (
|
90
76
|
id int not null auto_increment,
|
91
77
|
name varchar(50) not null,
|
92
78
|
primary key (id)
|
@@ -107,9 +93,9 @@ create table membership_statuses (
|
|
107
93
|
);
|
108
94
|
|
109
95
|
create table departments (
|
110
|
-
|
96
|
+
id int not null auto_increment,
|
111
97
|
location_id int not null,
|
112
|
-
primary key (
|
98
|
+
primary key (id, location_id)
|
113
99
|
);
|
114
100
|
|
115
101
|
create table employees (
|
@@ -122,16 +108,9 @@ create table employees (
|
|
122
108
|
|
123
109
|
create table comments (
|
124
110
|
id int not null auto_increment,
|
125
|
-
|
126
|
-
|
127
|
-
person_type varchar(100)
|
128
|
-
hack_id int default null,
|
129
|
-
primary key (id)
|
130
|
-
);
|
131
|
-
|
132
|
-
create table hacks (
|
133
|
-
id int not null auto_increment,
|
134
|
-
name varchar(50) not null,
|
111
|
+
article_id int not null,
|
112
|
+
person_id int not null,
|
113
|
+
person_type varchar(100) not null,
|
135
114
|
primary key (id)
|
136
115
|
);
|
137
116
|
|
@@ -184,13 +163,6 @@ create table room_assignments (
|
|
184
163
|
room_id int not null
|
185
164
|
);
|
186
165
|
|
187
|
-
create table seats (
|
188
|
-
flight_number int not null,
|
189
|
-
seat int not null,
|
190
|
-
customer int,
|
191
|
-
primary key (flight_number, seat)
|
192
|
-
);
|
193
|
-
|
194
166
|
create table capitols (
|
195
167
|
country varchar(100) not null,
|
196
168
|
city varchar(100) not null,
|
@@ -206,13 +178,4 @@ create table products_restaurants (
|
|
206
178
|
create table employees_groups (
|
207
179
|
employee_id int not null,
|
208
180
|
group_id int not null
|
209
|
-
);
|
210
|
-
|
211
|
-
create table pk_called_ids (
|
212
|
-
id integer not null,
|
213
|
-
reference_code int not null,
|
214
|
-
code_label varchar(50) default null,
|
215
|
-
abbreviation varchar(50) default null,
|
216
|
-
description varchar(50) default null,
|
217
|
-
primary key (id, reference_code)
|
218
181
|
);
|
@@ -1,6 +1,3 @@
|
|
1
|
-
drop table topics;
|
2
|
-
drop sequence topics_seq;
|
3
|
-
drop table topic_sources;
|
4
1
|
drop table reference_types;
|
5
2
|
drop sequence reference_types_seq;
|
6
3
|
drop table reference_codes;
|
@@ -9,6 +6,7 @@ drop sequence products_seq;
|
|
9
6
|
drop table tariffs;
|
10
7
|
drop table product_tariffs;
|
11
8
|
drop table suburbs;
|
9
|
+
drop sequence suburbs_city_id_seq;
|
12
10
|
drop table streets;
|
13
11
|
drop sequence streets_seq;
|
14
12
|
drop table users;
|
@@ -23,12 +21,11 @@ drop table memberships;
|
|
23
21
|
drop table membership_statuses;
|
24
22
|
drop sequence membership_statuses_seq;
|
25
23
|
drop table departments;
|
24
|
+
drop sequence departments_seq;
|
26
25
|
drop table employees;
|
27
26
|
drop sequence employees_seq;
|
28
27
|
drop table comments;
|
29
28
|
drop sequence comments_seq;
|
30
|
-
drop table hacks;
|
31
|
-
drop sequence hacks_seq;
|
32
29
|
drop table restaurants;
|
33
30
|
drop table restaurants_suburbs;
|
34
31
|
drop table dorms;
|
@@ -40,9 +37,6 @@ drop table room_attribute_assignments;
|
|
40
37
|
drop table room_assignments;
|
41
38
|
drop table students;
|
42
39
|
drop sequence students_seq;
|
43
|
-
drop table seats;
|
44
40
|
drop table capitols;
|
45
41
|
drop table products_restaurants;
|
46
|
-
drop table employees_groups;
|
47
|
-
drop table pk_called_ids;
|
48
|
-
drop sequence pk_called_ids_seq;
|
42
|
+
drop table employees_groups;
|