torque-postgresql 2.0.6 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|