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 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