File: pg_xmin_optimistic_locking_spec.rb

package info (click to toggle)
ruby-sequel 5.97.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 11,188 kB
  • sloc: ruby: 123,115; makefile: 3
file content (144 lines) | stat: -rw-r--r-- 5,096 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
require_relative "spec_helper"

pg_xmin_optimistic_locking_specs = Module.new do
  extend Minitest::Spec::DSL

  it "should include the xmin column in the model's datasets" do
    @c.dataset.sql.must_equal "SELECT *, xmin FROM items"
    @c.instance_dataset.sql.must_equal "SELECT *, xmin FROM items LIMIT 1"
  end

  it "should include the lock column when updating multiple times" do
    @db.fetch = [[{:xmin=>3}], [{:xmin => 4}]]
    @o.save
    @db.sqls.must_equal ["UPDATE items SET name = 'a' WHERE ((id = 1) AND (xmin = 2)) RETURNING xmin"]
    @o.save
    @db.sqls.must_equal ["UPDATE items SET name = 'a' WHERE ((id = 1) AND (xmin = 3)) RETURNING xmin"]
  end

  it "should not include the lock column if not present in the values" do
    @db.fetch = [[{:xmin=>3}], [{:xmin => 4}]]
    @o.values.delete(:xmin)
    @o.save
    @db.sqls.must_equal ["UPDATE items SET name = 'a' WHERE (id = 1) RETURNING xmin"]
    @o.save
    @db.sqls.must_equal ["UPDATE items SET name = 'a' WHERE ((id = 1) AND (xmin = 3)) RETURNING xmin"]
  end

  it "should include the primary key column when updating if it has changed" do
    @db.fetch = [[{:xmin=>3}]]
    @o.id = 4
    @o.save
    @db.sqls.must_equal ["UPDATE items SET id = 4, name = 'a' WHERE ((id = 4) AND (xmin = 2)) RETURNING xmin"]
  end

  it "should automatically update lock column using new value from database" do
    @db.fetch = [[{:xmin=>3}]]
    @o.save
    @o.xmin.must_equal 3
  end

  it "should raise error when updating stale object" do
    @db.fetch = []
    proc{@o.save}.must_raise(Sequel::NoExistingObject)
    @db.sqls.must_equal ["UPDATE items SET name = 'a' WHERE ((id = 1) AND (xmin = 2)) RETURNING xmin"]
    @o.xmin.must_equal 2
  end

  it "should raise error when destroying stale object" do
    @db.numrows = 0
    proc{@o.destroy}.must_raise(Sequel::NoExistingObject)
    @db.sqls.must_equal ["DELETE FROM items WHERE ((id = 1) AND (xmin = 2))"]
  end

  it "should allow refresh after failed save" do
    @db.fetch = []
    proc{@o.save}.must_raise(Sequel::NoExistingObject)
    @db.sqls.must_equal ["UPDATE items SET name = 'a' WHERE ((id = 1) AND (xmin = 2)) RETURNING xmin"]
    @db.fetch = {:id=>1, :name=>'a', :xmin =>3}
    @o.refresh
    @db.sqls.must_equal ["SELECT *, xmin FROM items WHERE (id = 1) LIMIT 1"]
    @o.save
    @db.sqls.must_equal ["UPDATE items SET name = 'a' WHERE ((id = 1) AND (xmin = 3)) RETURNING xmin"]
  end

  it "should work when subclassing" do
    c = Class.new(@c)
    o = c.load(:id=>1, :name=>'a', :xmin=>2)
    @db.fetch = [[{:xmin=>3}]]
    o.save
    @db.sqls.must_equal ["UPDATE items SET name = 'a' WHERE ((id = 1) AND (xmin = 2)) RETURNING xmin"]
  end
end

describe "pg_xmin_optimistic_locking plugin loaded into class with dataset" do
  before do
    @db = Sequel.mock(:host=>'postgres')
    @ds = @db[:items].with_quote_identifiers(false).with_extend do
      def columns!
        cs = [:id, :name]
        cs << :xmin if opts[:select] && opts[:select].include?(:xmin)
        cs
      end
    end
    @c = Class.new(Sequel::Model(@ds))
    @c.columns :id, :name
    @c.plugin :pg_xmin_optimistic_locking
    @o = @c.load(:id=>1, :name=>'a', :xmin=>2)
    @db.sqls
  end

  include pg_xmin_optimistic_locking_specs
end

describe "pg_xmin_optimistic_locking plugin loaded into base class" do
  before do
    @db = Sequel.mock(:host=>'postgres')
    @ds = @db[:items].with_quote_identifiers(false).with_extend do
      def columns!
        cs = [:id, :name]
        cs << :xmin if opts[:select] && opts[:select].include?(:xmin)
        cs
      end
    end
    @bc = Class.new(Sequel::Model)
    @bc.plugin :pg_xmin_optimistic_locking
    @c = @bc::Model(@ds)
    @c.columns :id, :name
    @o = @c.load(:id=>1, :name=>'a', :xmin=>2)
    @db.sqls
  end

  include pg_xmin_optimistic_locking_specs

  it "should handle datasets not selecting from tables" do
    ds = @ds.with_extend{def columns!; raise Sequel::DatabaseError if opts[:select] && opts[:select].include?(:xmin); super end}
    @c = @bc::Model(ds)
    @c.columns :id, :name
    @db.sqls

    @c.dataset.sql.must_equal "SELECT * FROM items"
    @c.instance_dataset.sql.must_equal "SELECT * FROM items LIMIT 1"
    @db.fetch = {:id=>1, :name=>'a'}
    @c.first.must_equal @c.load(:id=>1, :name=>'a')
    @db.sqls.must_equal ["SELECT * FROM items LIMIT 1"]
  end

  it "should handle datasets where returned columns do not include xmin" do
    ds = @ds.with_extend{def columns!; [:id, :name] end}
    @c = @bc::Model(ds)
    @c.columns :id, :name
    @db.sqls

    @c.dataset.sql.must_equal "SELECT * FROM items"
    @c.instance_dataset.sql.must_equal "SELECT * FROM items LIMIT 1"
    @db.fetch = {:id=>1, :name=>'a'}
    @c.first.must_equal @c.load(:id=>1, :name=>'a')
    @db.sqls.must_equal ["SELECT * FROM items LIMIT 1"]
  end

  it "should raise connection errors when loading" do
    ds = @ds.with_extend{def columns!; raise Sequel::DatabaseConnectionError if opts[:select] && opts[:select].include?(:xmin); super end}
    proc{@c = @bc::Model(ds)}.must_raise Sequel::DatabaseConnectionError
  end
end