torque-postgresql 2.0.6 → 2.1.0
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/lib/torque/postgresql.rb +1 -0
- data/lib/torque/postgresql/adapter.rb +16 -0
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +9 -4
- data/lib/torque/postgresql/base.rb +6 -0
- data/lib/torque/postgresql/insert_all.rb +26 -0
- data/lib/torque/postgresql/version.rb +1 -1
- data/spec/tests/belongs_to_many_spec.rb +30 -1
- data/spec/tests/insert_all_spec.rb +89 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd2096d27cb0b11fd4cd2048627bf0b12dcd905d4240ea0251366f580a0afcf2
|
4
|
+
data.tar.gz: c6d948dd77768feff3d85284bc838b8fb03c91233cc33ecdc8b3c22e463038c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f61ba4bf68f2519090bb23b259cf9d8fa2c59f5a9935c867fe0dd2629eeeddd21e7a9f68a0e168550f696564d9613a4617ed6619ac4fe1f3d7660bf3fd1fc9c
|
7
|
+
data.tar.gz: dbb298a6df589aa9368fb70f933b5ece9984bfc3661d667c7e42b989e2bdda6159aafbf78a303f9926f3b17dbc762473454f4646e91e976a21a0fd439a57c9f9
|
data/lib/torque/postgresql.rb
CHANGED
@@ -22,6 +22,7 @@ require 'torque/postgresql/autosave_association'
|
|
22
22
|
require 'torque/postgresql/auxiliary_statement'
|
23
23
|
require 'torque/postgresql/base'
|
24
24
|
require 'torque/postgresql/inheritance'
|
25
|
+
require 'torque/postgresql/insert_all'
|
25
26
|
require 'torque/postgresql/coder'
|
26
27
|
require 'torque/postgresql/migration'
|
27
28
|
require 'torque/postgresql/relation'
|
@@ -15,6 +15,8 @@ module Torque
|
|
15
15
|
include DatabaseStatements
|
16
16
|
include SchemaStatements
|
17
17
|
|
18
|
+
INJECT_WHERE_REGEX = /(DO UPDATE SET.*excluded\.[^ ]+) RETURNING/.freeze
|
19
|
+
|
18
20
|
# Get the current PostgreSQL version as a Gem Version.
|
19
21
|
def version
|
20
22
|
@version ||= Gem::Version.new(
|
@@ -26,6 +28,20 @@ module Torque
|
|
26
28
|
def extract_table_options!(options)
|
27
29
|
super.merge(options.extract!(:inherits))
|
28
30
|
end
|
31
|
+
|
32
|
+
# Allow filtered bulk insert by adding the where clause. This method is only used by
|
33
|
+
# +InsertAll+, so it somewhat safe to override it
|
34
|
+
def build_insert_sql(insert)
|
35
|
+
super.tap do |sql|
|
36
|
+
if insert.update_duplicates? && insert.where_condition?
|
37
|
+
if insert.returning
|
38
|
+
sql.gsub!(INJECT_WHERE_REGEX, "\\1 WHERE #{insert.where} RETURNING")
|
39
|
+
else
|
40
|
+
sql << " WHERE #{insert.where}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
29
45
|
end
|
30
46
|
|
31
47
|
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend Adapter
|
@@ -16,15 +16,16 @@ module Torque
|
|
16
16
|
elsif !target.empty?
|
17
17
|
load_target.pluck(reflection.association_primary_key)
|
18
18
|
else
|
19
|
-
stale_state
|
19
|
+
stale_state || column_default_value
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
def ids_writer(ids)
|
24
|
-
|
24
|
+
ids = ids.presence || column_default_value
|
25
|
+
owner.write_attribute(source_attr, ids)
|
25
26
|
return unless owner.persisted? && owner.attribute_changed?(source_attr)
|
26
27
|
|
27
|
-
owner.update_attribute(source_attr, ids
|
28
|
+
owner.update_attribute(source_attr, ids)
|
28
29
|
end
|
29
30
|
|
30
31
|
def size
|
@@ -179,12 +180,16 @@ module Torque
|
|
179
180
|
def ids_rewriter(ids, operator)
|
180
181
|
list = owner[source_attr] ||= []
|
181
182
|
list = list.public_send(operator, ids)
|
182
|
-
owner[source_attr] = list.uniq.compact.presence
|
183
|
+
owner[source_attr] = list.uniq.compact.presence || column_default_value
|
183
184
|
|
184
185
|
return if @_building_changes || !owner.persisted?
|
185
186
|
owner.update_attribute(source_attr, list)
|
186
187
|
end
|
187
188
|
|
189
|
+
def column_default_value
|
190
|
+
owner.class.columns_hash[source_attr].default
|
191
|
+
end
|
192
|
+
|
188
193
|
## HAS MANY
|
189
194
|
def replace_records(*)
|
190
195
|
build_changes { super }
|
@@ -206,6 +206,12 @@ module Torque
|
|
206
206
|
::ActiveRecord::Reflection.add_reflection(self, name, reflection)
|
207
207
|
end
|
208
208
|
|
209
|
+
# Allow extra keyword arguments to be sent to +InsertAll+
|
210
|
+
def upsert_all(attributes, **xargs)
|
211
|
+
xargs = xargs.merge(on_duplicate: :update)
|
212
|
+
::ActiveRecord::InsertAll.new(self, attributes, **xargs).execute
|
213
|
+
end
|
214
|
+
|
209
215
|
protected
|
210
216
|
|
211
217
|
# Allow optional select attributes to be loaded manually when they are
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
module InsertAll
|
6
|
+
attr_reader :where
|
7
|
+
|
8
|
+
def initialize(*args, where: nil, **xargs)
|
9
|
+
super(*args, **xargs)
|
10
|
+
|
11
|
+
@where = where
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module InsertAll::Builder
|
16
|
+
delegate :where, to: :insert_all
|
17
|
+
|
18
|
+
def where_condition?
|
19
|
+
!where.nil?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
ActiveRecord::InsertAll.prepend InsertAll
|
24
|
+
ActiveRecord::InsertAll::Builder.include InsertAll::Builder
|
25
|
+
end
|
26
|
+
end
|
@@ -318,7 +318,36 @@ RSpec.describe 'BelongsToMany' do
|
|
318
318
|
expect { query.load }.not_to raise_error
|
319
319
|
end
|
320
320
|
|
321
|
-
context
|
321
|
+
context 'When the attribute has a default value' do
|
322
|
+
after(:all) { Video.reset_column_information }
|
323
|
+
let(:sql) { %{ALTER TABLE "videos" ALTER COLUMN "tag_ids" SET DEFAULT '{}'::bigint[]} }
|
324
|
+
|
325
|
+
before do
|
326
|
+
Video.connection.execute(sql)
|
327
|
+
Video.reset_column_information
|
328
|
+
end
|
329
|
+
|
330
|
+
it 'will always return the column default value' do
|
331
|
+
expect(subject.tag_ids).to be_a(Array)
|
332
|
+
expect(subject.tag_ids).to be_empty
|
333
|
+
end
|
334
|
+
|
335
|
+
it 'will keep the value as an array even when the association is cleared' do
|
336
|
+
records = FactoryBot.create_list(:tag, 5)
|
337
|
+
subject.tags.concat(records)
|
338
|
+
|
339
|
+
subject.reload
|
340
|
+
expect(subject.tag_ids).to be_a(Array)
|
341
|
+
expect(subject.tag_ids).not_to be_empty
|
342
|
+
|
343
|
+
subject.tags.clear
|
344
|
+
subject.reload
|
345
|
+
expect(subject.tag_ids).to be_a(Array)
|
346
|
+
expect(subject.tag_ids).to be_empty
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
context 'When record is not persisted' do
|
322
351
|
let(:initial) { FactoryBot.create(:tag) }
|
323
352
|
|
324
353
|
subject { Video.new(title: 'A', tags: [initial]) }
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'InsertAll' do
|
4
|
+
context 'on executing' do
|
5
|
+
before do
|
6
|
+
ActiveRecord::InsertAll.send(:public, :to_sql)
|
7
|
+
allow_any_instance_of(ActiveRecord::InsertAll).to receive(:execute, &:to_sql)
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { Tag }
|
11
|
+
|
12
|
+
let(:entries) { [{ name: 'A' }, { name: 'B' }] }
|
13
|
+
|
14
|
+
it 'does not mess with insert_all' do
|
15
|
+
result = subject.insert_all(entries)
|
16
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
17
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
18
|
+
ON CONFLICT DO NOTHING RETURNING "id"
|
19
|
+
SQL
|
20
|
+
|
21
|
+
result = subject.insert_all(entries, returning: %i[name])
|
22
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
23
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
24
|
+
ON CONFLICT DO NOTHING RETURNING "name"
|
25
|
+
SQL
|
26
|
+
|
27
|
+
result = subject.insert_all(entries, returning: %i[id name])
|
28
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
29
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
30
|
+
ON CONFLICT DO NOTHING RETURNING "id","name"
|
31
|
+
SQL
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'does not mess with insert_all!' do
|
35
|
+
result = subject.insert_all!(entries)
|
36
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
37
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B') RETURNING "id"
|
38
|
+
SQL
|
39
|
+
|
40
|
+
result = subject.insert_all!(entries, returning: %i[name])
|
41
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
42
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B') RETURNING "name"
|
43
|
+
SQL
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'does not mess with upsert without where' do
|
47
|
+
result = subject.upsert_all(entries)
|
48
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
49
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
50
|
+
ON CONFLICT ("id") DO UPDATE SET "name"=excluded."name"
|
51
|
+
RETURNING "id"
|
52
|
+
SQL
|
53
|
+
|
54
|
+
result = subject.upsert_all(entries, returning: %i[name])
|
55
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
56
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
57
|
+
ON CONFLICT ("id") DO UPDATE SET "name"=excluded."name"
|
58
|
+
RETURNING "name"
|
59
|
+
SQL
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'does add the where condition without the returning clause' do
|
63
|
+
result = subject.upsert_all(entries, returning: false, where: '1=1')
|
64
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
65
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
66
|
+
ON CONFLICT ("id") DO UPDATE SET "name"=excluded."name"
|
67
|
+
WHERE 1=1
|
68
|
+
SQL
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'does add the where condition with the returning clause' do
|
72
|
+
result = subject.upsert_all(entries, where: '1=1')
|
73
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
74
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
75
|
+
ON CONFLICT ("id") DO UPDATE SET "name"=excluded."name"
|
76
|
+
WHERE 1=1 RETURNING "id"
|
77
|
+
SQL
|
78
|
+
end
|
79
|
+
|
80
|
+
xit 'dows work with model-based where clause' do
|
81
|
+
result = subject.upsert_all(entries, where: Tag.where(name: 'C'))
|
82
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
83
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
84
|
+
ON CONFLICT ("id") DO UPDATE SET "name"=excluded."name"
|
85
|
+
WHERE "tags"."name" = 'C' RETURNING "id"
|
86
|
+
SQL
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: torque-postgresql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carlos Silva
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-02-
|
11
|
+
date: 2021-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -221,6 +221,7 @@ files:
|
|
221
221
|
- lib/torque/postgresql/geometry_builder.rb
|
222
222
|
- lib/torque/postgresql/i18n.rb
|
223
223
|
- lib/torque/postgresql/inheritance.rb
|
224
|
+
- lib/torque/postgresql/insert_all.rb
|
224
225
|
- lib/torque/postgresql/migration.rb
|
225
226
|
- lib/torque/postgresql/migration/command_recorder.rb
|
226
227
|
- lib/torque/postgresql/railtie.rb
|
@@ -277,6 +278,7 @@ files:
|
|
277
278
|
- spec/tests/enum_spec.rb
|
278
279
|
- spec/tests/geometric_builder_spec.rb
|
279
280
|
- spec/tests/has_many_spec.rb
|
281
|
+
- spec/tests/insert_all_spec.rb
|
280
282
|
- spec/tests/interval_spec.rb
|
281
283
|
- spec/tests/lazy_spec.rb
|
282
284
|
- spec/tests/period_spec.rb
|
@@ -334,6 +336,7 @@ test_files:
|
|
334
336
|
- spec/tests/geometric_builder_spec.rb
|
335
337
|
- spec/tests/range_spec.rb
|
336
338
|
- spec/tests/arel_spec.rb
|
339
|
+
- spec/tests/insert_all_spec.rb
|
337
340
|
- spec/tests/enum_spec.rb
|
338
341
|
- spec/tests/period_spec.rb
|
339
342
|
- spec/tests/coder_spec.rb
|