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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd314ab034c3d38a227829faf2d706b13625a5e4cc6393004f6dbe2890e784a8
4
- data.tar.gz: 035b0fc92e1ea2aedb507c18094e66025c0fe98838f8553f3abc64764a0b2f03
3
+ metadata.gz: cd2096d27cb0b11fd4cd2048627bf0b12dcd905d4240ea0251366f580a0afcf2
4
+ data.tar.gz: c6d948dd77768feff3d85284bc838b8fb03c91233cc33ecdc8b3c22e463038c3
5
5
  SHA512:
6
- metadata.gz: 6eeaffde8209891fee13de7518acea135cfe5f35ecbc5aa59124faad208ed0545a24c2ffb993f10eb5e306c2ccb96036e1dbc812a11177ecdda5e3ead62e0dd7
7
- data.tar.gz: ad55cf8b8a5e9de71575c12e8452d440439529258752ceceb95da17111991b8a9ad90101806ab328d5993d328bec69a71feb4cc6b85431c9802d0c225557dbb5
6
+ metadata.gz: 3f61ba4bf68f2519090bb23b259cf9d8fa2c59f5a9935c867fe0dd2629eeeddd21e7a9f68a0e168550f696564d9613a4617ed6619ac4fe1f3d7660bf3fd1fc9c
7
+ data.tar.gz: dbb298a6df589aa9368fb70f933b5ece9984bfc3661d667c7e42b989e2bdda6159aafbf78a303f9926f3b17dbc762473454f4646e91e976a21a0fd439a57c9f9
@@ -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
- owner.write_attribute(source_attr, ids.presence)
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.presence)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Torque
4
4
  module PostgreSQL
5
- VERSION = '2.0.6'
5
+ VERSION = '2.1.0'
6
6
  end
7
7
  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 "When record is not persisted" do
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.6
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-10 00:00:00.000000000 Z
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